Sun Oct 16 2011 08:41:38

Asterisk developer's documentation


config.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2010, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Configuration File Parser
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  *
00025  * Includes the Asterisk Realtime API - ARA
00026  * See http://wiki.asterisk.org
00027  */
00028 
00029 #include "asterisk.h"
00030 
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 334296 $")
00032 
00033 #include "asterisk/paths.h"   /* use ast_config_AST_CONFIG_DIR */
00034 #include "asterisk/network.h" /* we do some sockaddr manipulation here */
00035 #include <time.h>
00036 #include <sys/stat.h>
00037 
00038 #include <math.h> /* HUGE_VAL */
00039 
00040 #define AST_INCLUDE_GLOB 1
00041 
00042 #include "asterisk/config.h"
00043 #include "asterisk/cli.h"
00044 #include "asterisk/lock.h"
00045 #include "asterisk/utils.h"
00046 #include "asterisk/channel.h"
00047 #include "asterisk/app.h"
00048 #include "asterisk/astobj2.h"
00049 #include "asterisk/strings.h" /* for the ast_str_*() API */
00050 #include "asterisk/netsock2.h"
00051 
00052 #define MAX_NESTED_COMMENTS 128
00053 #define COMMENT_START ";--"
00054 #define COMMENT_END "--;"
00055 #define COMMENT_META ';'
00056 #define COMMENT_TAG '-'
00057 
00058 /*!
00059  * Define the minimum filename space to reserve for each
00060  * ast_variable in case the filename is renamed later by
00061  * ast_include_rename().
00062  */
00063 #define MIN_VARIABLE_FNAME_SPACE 40
00064 
00065 static char *extconfig_conf = "extconfig.conf";
00066 
00067 
00068 /*! \brief Structure to keep comments for rewriting configuration files */
00069 struct ast_comment {
00070    struct ast_comment *next;
00071    /*! Comment body allocated after struct. */
00072    char cmt[0];
00073 };
00074 
00075 /*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
00076 struct cache_file_include {
00077    AST_LIST_ENTRY(cache_file_include) list;
00078    char include[0];
00079 };
00080 
00081 struct cache_file_mtime {
00082    AST_LIST_ENTRY(cache_file_mtime) list;
00083    AST_LIST_HEAD_NOLOCK(includes, cache_file_include) includes;
00084    unsigned int has_exec:1;
00085    time_t mtime;
00086 
00087    /*! String stuffed in filename[] after the filename string. */
00088    const char *who_asked;
00089    /*! Filename and who_asked stuffed after it. */
00090    char filename[0];
00091 };
00092 
00093 /*! Cached file mtime list. */
00094 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
00095 
00096 static int init_appendbuf(void *data)
00097 {
00098    struct ast_str **str = data;
00099    *str = ast_str_create(16);
00100    return *str ? 0 : -1;
00101 }
00102 
00103 AST_THREADSTORAGE_CUSTOM(appendbuf, init_appendbuf, ast_free_ptr);
00104 
00105 /* comment buffers are better implemented using the ast_str_*() API */
00106 #define CB_SIZE 250  /* initial size of comment buffers */
00107 
00108 static void  CB_ADD(struct ast_str **cb, const char *str)
00109 {
00110    ast_str_append(cb, 0, "%s", str);
00111 }
00112 
00113 static void  CB_ADD_LEN(struct ast_str **cb, const char *str, int len)
00114 {
00115    char *s = alloca(len + 1);
00116    ast_copy_string(s, str, len);
00117    ast_str_append(cb, 0, "%s", str);
00118 }
00119 
00120 static void CB_RESET(struct ast_str *cb, struct ast_str *llb)  
00121 { 
00122    if (cb) {
00123       ast_str_reset(cb);
00124    }
00125    if (llb) {
00126       ast_str_reset(llb);
00127    }
00128 }
00129 
00130 static struct ast_comment *ALLOC_COMMENT(struct ast_str *buffer)
00131 { 
00132    struct ast_comment *x = NULL;
00133    if (!buffer || !ast_str_strlen(buffer)) {
00134       return NULL;
00135    }
00136    if ((x = ast_calloc(1, sizeof(*x) + ast_str_strlen(buffer) + 1))) {
00137       strcpy(x->cmt, ast_str_buffer(buffer)); /* SAFE */
00138    }
00139    return x;
00140 }
00141 
00142 /* I need to keep track of each config file, and all its inclusions,
00143    so that we can track blank lines in each */
00144 
00145 struct inclfile {
00146    char *fname;
00147    int lineno;
00148 };
00149 
00150 static int hash_string(const void *obj, const int flags)
00151 {
00152    char *str = ((struct inclfile *) obj)->fname;
00153    int total;
00154 
00155    for (total = 0; *str; str++) {
00156       unsigned int tmp = total;
00157       total <<= 1; /* multiply by 2 */
00158       total += tmp; /* multiply by 3 */
00159       total <<= 2; /* multiply by 12 */
00160       total += tmp; /* multiply by 13 */
00161 
00162       total += ((unsigned int) (*str));
00163    }
00164    if (total < 0) {
00165       total = -total;
00166    }
00167    return total;
00168 }
00169 
00170 static int hashtab_compare_strings(void *a, void *b, int flags)
00171 {
00172    const struct inclfile *ae = a, *be = b;
00173    return !strcmp(ae->fname, be->fname) ? CMP_MATCH | CMP_STOP : 0;
00174 }
00175 
00176 static struct ast_config_map {
00177    struct ast_config_map *next;
00178    int priority;
00179    /*! Stored in stuff[] at struct end. */
00180    const char *name;
00181    /*! Stored in stuff[] at struct end. */
00182    const char *driver;
00183    /*! Stored in stuff[] at struct end. */
00184    const char *database;
00185    /*! Stored in stuff[] at struct end. */
00186    const char *table;
00187    /*! Contents of name, driver, database, and table in that order stuffed here. */
00188    char stuff[0];
00189 } *config_maps = NULL;
00190 
00191 AST_MUTEX_DEFINE_STATIC(config_lock);
00192 static struct ast_config_engine *config_engine_list;
00193 
00194 #define MAX_INCLUDE_LEVEL 10
00195 
00196 struct ast_category_template_instance {
00197    char name[80]; /* redundant? */
00198    const struct ast_category *inst;
00199    AST_LIST_ENTRY(ast_category_template_instance) next;
00200 };
00201 
00202 struct ast_category {
00203    char name[80];
00204    int ignored;         /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
00205    int include_level;
00206    /*!
00207     * \brief The file name from whence this declaration was read
00208     * \note Will never be NULL
00209     */
00210    char *file;
00211    int lineno;
00212    AST_LIST_HEAD_NOLOCK(template_instance_list, ast_category_template_instance) template_instances;
00213    struct ast_comment *precomments;
00214    struct ast_comment *sameline;
00215    struct ast_comment *trailing; /*!< the last object in the list will get assigned any trailing comments when EOF is hit */
00216    /*! First category variable in the list. */
00217    struct ast_variable *root;
00218    /*! Last category variable in the list. */
00219    struct ast_variable *last;
00220    /*! Next node in the list. */
00221    struct ast_category *next;
00222 };
00223 
00224 struct ast_config {
00225    /*! First config category in the list. */
00226    struct ast_category *root;
00227    /*! Last config category in the list. */
00228    struct ast_category *last;
00229    struct ast_category *current;
00230    struct ast_category *last_browse;     /*!< used to cache the last category supplied via category_browse */
00231    int include_level;
00232    int max_include_level;
00233    struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
00234 };
00235 
00236 struct ast_config_include {
00237    /*!
00238     * \brief file name in which the include occurs
00239     * \note Will never be NULL
00240     */
00241    char *include_location_file;
00242    int  include_location_lineno;    /*!< lineno where include occurred */
00243    int  exec;                       /*!< set to non-zero if its a #exec statement */
00244    /*!
00245     * \brief if it's an exec, you'll have both the /var/tmp to read, and the original script
00246     * \note Will never be NULL if exec is non-zero
00247     */
00248    char *exec_file;
00249    /*!
00250     * \brief file name included
00251     * \note Will never be NULL
00252     */
00253    char *included_file;
00254    int inclusion_count;             /*!< if the file is included more than once, a running count thereof -- but, worry not,
00255                                          we explode the instances and will include those-- so all entries will be unique */
00256    int output;                      /*!< a flag to indicate if the inclusion has been output */
00257    struct ast_config_include *next; /*!< ptr to next inclusion in the list */
00258 };
00259 
00260 static void ast_variable_destroy(struct ast_variable *doomed);
00261 static void ast_includes_destroy(struct ast_config_include *incls);
00262 
00263 #ifdef MALLOC_DEBUG
00264 struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno)
00265 #else
00266 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename)
00267 #endif
00268 {
00269    struct ast_variable *variable;
00270    int name_len = strlen(name) + 1;
00271    int val_len = strlen(value) + 1;
00272    int fn_len = strlen(filename) + 1;
00273 
00274    /* Ensure a minimum length in case the filename is changed later. */
00275    if (fn_len < MIN_VARIABLE_FNAME_SPACE) {
00276       fn_len = MIN_VARIABLE_FNAME_SPACE;
00277    }
00278 
00279    if (
00280 #ifdef MALLOC_DEBUG
00281       (variable = __ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable), file, lineno, func))
00282 #else
00283       (variable = ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable)))
00284 #endif
00285       ) {
00286       char *dst = variable->stuff;  /* writable space starts here */
00287 
00288       /* Put file first so ast_include_rename() can calculate space available. */
00289       variable->file = strcpy(dst, filename);
00290       dst += fn_len;
00291       variable->name = strcpy(dst, name);
00292       dst += name_len;
00293       variable->value = strcpy(dst, value);
00294    }
00295    return variable;
00296 }
00297 
00298 /*!
00299  * \internal
00300  * \brief Move the contents from the source to the destination variable.
00301  *
00302  * \param dst_var Destination variable node
00303  * \param src_var Source variable node
00304  *
00305  * \return Nothing
00306  */
00307 static void ast_variable_move(struct ast_variable *dst_var, struct ast_variable *src_var)
00308 {
00309    dst_var->lineno = src_var->lineno;
00310    dst_var->object = src_var->object;
00311    dst_var->blanklines = src_var->blanklines;
00312    dst_var->precomments = src_var->precomments;
00313    src_var->precomments = NULL;
00314    dst_var->sameline = src_var->sameline;
00315    src_var->sameline = NULL;
00316    dst_var->trailing = src_var->trailing;
00317    src_var->trailing = NULL;
00318 }
00319 
00320 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)
00321 {
00322    /* a file should be included ONCE. Otherwise, if one of the instances is changed,
00323     * then all be changed. -- how do we know to include it? -- Handling modified 
00324     * instances is possible, I'd have
00325     * to create a new master for each instance. */
00326    struct ast_config_include *inc;
00327    struct stat statbuf;
00328    
00329    inc = ast_include_find(conf, included_file);
00330    if (inc) {
00331       do {
00332          inc->inclusion_count++;
00333          snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
00334       } while (stat(real_included_file_name, &statbuf) == 0);
00335       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);
00336    } else
00337       *real_included_file_name = 0;
00338    
00339    inc = ast_calloc(1,sizeof(struct ast_config_include));
00340    if (!inc) {
00341       return NULL;
00342    }
00343    inc->include_location_file = ast_strdup(from_file);
00344    inc->include_location_lineno = from_lineno;
00345    if (!ast_strlen_zero(real_included_file_name))
00346       inc->included_file = ast_strdup(real_included_file_name);
00347    else
00348       inc->included_file = ast_strdup(included_file);
00349    
00350    inc->exec = is_exec;
00351    if (is_exec)
00352       inc->exec_file = ast_strdup(exec_file);
00353 
00354    if (!inc->include_location_file
00355       || !inc->included_file
00356       || (is_exec && !inc->exec_file)) {
00357       ast_includes_destroy(inc);
00358       return NULL;
00359    }
00360 
00361    /* attach this new struct to the conf struct */
00362    inc->next = conf->includes;
00363    conf->includes = inc;
00364    
00365    return inc;
00366 }
00367 
00368 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
00369 {
00370    struct ast_config_include *incl;
00371    struct ast_category *cat;
00372    char *str;
00373 
00374    int from_len = strlen(from_file);
00375    int to_len = strlen(to_file);
00376    
00377    if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
00378       return;
00379    
00380    /* the manager code allows you to read in one config file, then
00381     * write it back out under a different name. But, the new arrangement
00382     * ties output lines to the file name. So, before you try to write
00383     * the config file to disk, better riffle thru the data and make sure
00384     * the file names are changed.
00385     */
00386    /* file names are on categories, includes (of course), and on variables. So,
00387     * traverse all this and swap names */
00388 
00389    for (incl = conf->includes; incl; incl=incl->next) {
00390       if (strcmp(incl->include_location_file,from_file) == 0) {
00391          if (from_len >= to_len)
00392             strcpy(incl->include_location_file, to_file);
00393          else {
00394             /* Keep the old filename if the allocation fails. */
00395             str = ast_strdup(to_file);
00396             if (str) {
00397                ast_free(incl->include_location_file);
00398                incl->include_location_file = str;
00399             }
00400          }
00401       }
00402    }
00403    for (cat = conf->root; cat; cat = cat->next) {
00404       struct ast_variable **prev;
00405       struct ast_variable *v;
00406       struct ast_variable *new_var;
00407 
00408       if (strcmp(cat->file,from_file) == 0) {
00409          if (from_len >= to_len)
00410             strcpy(cat->file, to_file);
00411          else {
00412             /* Keep the old filename if the allocation fails. */
00413             str = ast_strdup(to_file);
00414             if (str) {
00415                ast_free(cat->file);
00416                cat->file = str;
00417             }
00418          }
00419       }
00420       for (prev = &cat->root, v = cat->root; v; prev = &v->next, v = v->next) {
00421          if (strcmp(v->file, from_file)) {
00422             continue;
00423          }
00424 
00425          /*
00426           * Calculate actual space available.  The file string is
00427           * intentionally stuffed before the name string just so we can
00428           * do this.
00429           */
00430          if (to_len < v->name - v->file) {
00431             /* The new name will fit in the available space. */
00432             str = (char *) v->file;/* Stupid compiler complains about discarding qualifiers even though I used a cast. */
00433             strcpy(str, to_file);/* SAFE */
00434             continue;
00435          }
00436 
00437          /* Keep the old filename if the allocation fails. */
00438          new_var = ast_variable_new(v->name, v->value, to_file);
00439          if (!new_var) {
00440             continue;
00441          }
00442 
00443          /* Move items from the old list node to the replacement node. */
00444          ast_variable_move(new_var, v);
00445 
00446          /* Replace the old node in the list with the new node. */
00447          new_var->next = v->next;
00448          if (cat->last == v) {
00449             cat->last = new_var;
00450          }
00451          *prev = new_var;
00452 
00453          ast_variable_destroy(v);
00454 
00455          v = new_var;
00456       }
00457    }
00458 }
00459 
00460 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
00461 {
00462    struct ast_config_include *x;
00463    for (x=conf->includes;x;x=x->next) {
00464       if (strcmp(x->included_file,included_file) == 0)
00465          return x;
00466    }
00467    return 0;
00468 }
00469 
00470 
00471 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
00472 {
00473    if (!variable)
00474       return;
00475    if (category->last)
00476       category->last->next = variable;
00477    else
00478       category->root = variable;
00479    category->last = variable;
00480    while (category->last->next)
00481       category->last = category->last->next;
00482 }
00483 
00484 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
00485 {
00486    struct ast_variable *cur = category->root;
00487    int lineno;
00488    int insertline;
00489 
00490    if (!variable || sscanf(line, "%30d", &insertline) != 1) {
00491       return;
00492    }
00493    if (!insertline) {
00494       variable->next = category->root;
00495       category->root = variable;
00496    } else {
00497       for (lineno = 1; lineno < insertline; lineno++) {
00498          cur = cur->next;
00499          if (!cur->next) {
00500             break;
00501          }
00502       }
00503       variable->next = cur->next;
00504       cur->next = variable;
00505    }
00506 }
00507 
00508 static void ast_comment_destroy(struct ast_comment **comment)
00509 {
00510    struct ast_comment *n, *p;
00511 
00512    for (p = *comment; p; p = n) {
00513       n = p->next;
00514       ast_free(p);
00515    }
00516 
00517    *comment = NULL;
00518 }
00519 
00520 static void ast_variable_destroy(struct ast_variable *doomed)
00521 {
00522    ast_comment_destroy(&doomed->precomments);
00523    ast_comment_destroy(&doomed->sameline);
00524    ast_comment_destroy(&doomed->trailing);
00525    ast_free(doomed);
00526 }
00527 
00528 void ast_variables_destroy(struct ast_variable *v)
00529 {
00530    struct ast_variable *vn;
00531 
00532    while (v) {
00533       vn = v;
00534       v = v->next;
00535       ast_variable_destroy(vn);
00536    }
00537 }
00538 
00539 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
00540 {
00541    struct ast_category *cat = NULL;
00542 
00543    if (category && config->last_browse && (config->last_browse->name == category)) {
00544       cat = config->last_browse;
00545    } else {
00546       cat = ast_category_get(config, category);
00547    }
00548 
00549    return (cat) ? cat->root : NULL;
00550 }
00551 
00552 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
00553 {
00554    const char *tmp;
00555    tmp = ast_variable_retrieve(cfg, cat, var);
00556    if (!tmp) {
00557       tmp = ast_variable_retrieve(cfg, "general", var);
00558    }
00559    return tmp;
00560 }
00561 
00562 
00563 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
00564 {
00565    struct ast_variable *v;
00566 
00567    if (category) {
00568       for (v = ast_variable_browse(config, category); v; v = v->next) {
00569          if (!strcasecmp(variable, v->name)) {
00570             return v->value;
00571          }
00572       }
00573    } else {
00574       struct ast_category *cat;
00575 
00576       for (cat = config->root; cat; cat = cat->next) {
00577          for (v = cat->root; v; v = v->next) {
00578             if (!strcasecmp(variable, v->name)) {
00579                return v->value;
00580             }
00581          }
00582       }
00583    }
00584 
00585    return NULL;
00586 }
00587 
00588 static struct ast_variable *variable_clone(const struct ast_variable *old)
00589 {
00590    struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
00591 
00592    if (new) {
00593       new->lineno = old->lineno;
00594       new->object = old->object;
00595       new->blanklines = old->blanklines;
00596       /* TODO: clone comments? */
00597    }
00598 
00599    return new;
00600 }
00601  
00602 static void move_variables(struct ast_category *old, struct ast_category *new)
00603 {
00604    struct ast_variable *var = old->root;
00605 
00606    old->root = NULL;
00607    /* we can just move the entire list in a single op */
00608    ast_variable_append(new, var);
00609 }
00610 
00611 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) 
00612 {
00613    struct ast_category *category;
00614 
00615    category = ast_calloc(1, sizeof(*category));
00616    if (!category) {
00617       return NULL;
00618    }
00619    category->file = ast_strdup(in_file);
00620    if (!category->file) {
00621       ast_category_destroy(category);
00622       return NULL;
00623    }
00624    ast_copy_string(category->name, name, sizeof(category->name));
00625    category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
00626    return category;
00627 }
00628 
00629 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
00630 {
00631    struct ast_category *cat;
00632 
00633    /* try exact match first, then case-insensitive match */
00634    for (cat = config->root; cat; cat = cat->next) {
00635       if (cat->name == category_name && (ignored || !cat->ignored))
00636          return cat;
00637    }
00638 
00639    for (cat = config->root; cat; cat = cat->next) {
00640       if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
00641          return cat;
00642    }
00643 
00644    return NULL;
00645 }
00646 
00647 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
00648 {
00649    return category_get(config, category_name, 0);
00650 }
00651 
00652 int ast_category_exist(const struct ast_config *config, const char *category_name)
00653 {
00654    return !!ast_category_get(config, category_name);
00655 }
00656 
00657 void ast_category_append(struct ast_config *config, struct ast_category *category)
00658 {
00659    if (config->last)
00660       config->last->next = category;
00661    else
00662       config->root = category;
00663    category->include_level = config->include_level;
00664    config->last = category;
00665    config->current = category;
00666 }
00667 
00668 void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
00669 {
00670    struct ast_category *cur_category;
00671 
00672    if (!cat || !match)
00673       return;
00674    if (!strcasecmp(config->root->name, match)) {
00675       cat->next = config->root;
00676       config->root = cat;
00677       return;
00678    } 
00679    for (cur_category = config->root; cur_category; cur_category = cur_category->next) {
00680       if (!strcasecmp(cur_category->next->name, match)) {
00681          cat->next = cur_category->next;
00682          cur_category->next = cat;
00683          break;
00684       }
00685    }
00686 }
00687 
00688 static void ast_destroy_template_list(struct ast_category *cat)
00689 {
00690    struct ast_category_template_instance *x;
00691 
00692    while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
00693       ast_free(x);
00694 }
00695 
00696 void ast_category_destroy(struct ast_category *cat)
00697 {
00698    ast_variables_destroy(cat->root);
00699    cat->root = NULL;
00700    cat->last = NULL;
00701    ast_comment_destroy(&cat->precomments);
00702    ast_comment_destroy(&cat->sameline);
00703    ast_comment_destroy(&cat->trailing);
00704    ast_destroy_template_list(cat);
00705    ast_free(cat->file);
00706    ast_free(cat);
00707 }
00708 
00709 static void ast_includes_destroy(struct ast_config_include *incls)
00710 {
00711    struct ast_config_include *incl,*inclnext;
00712    
00713    for (incl=incls; incl; incl = inclnext) {
00714       inclnext = incl->next;
00715       ast_free(incl->include_location_file);
00716       ast_free(incl->exec_file);
00717       ast_free(incl->included_file);
00718       ast_free(incl);
00719    }
00720 }
00721 
00722 static struct ast_category *next_available_category(struct ast_category *cat)
00723 {
00724    for (; cat && cat->ignored; cat = cat->next);
00725 
00726    return cat;
00727 }
00728 
00729 /*! return the first var of a category */
00730 struct ast_variable *ast_category_first(struct ast_category *cat)
00731 {
00732    return (cat) ? cat->root : NULL;
00733 }
00734 
00735 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
00736 {
00737    struct ast_category *category = ast_category_get(config, cat);
00738 
00739    if (category)
00740       return category->root;
00741    return NULL;
00742 }
00743 
00744 char *ast_category_browse(struct ast_config *config, const char *prev)
00745 {  
00746    struct ast_category *cat;
00747 
00748    if (!prev) {
00749       /* First time browse. */
00750       cat = config->root;
00751    } else if (config->last_browse && (config->last_browse->name == prev)) {
00752       /* Simple last browse found. */
00753       cat = config->last_browse->next;
00754    } else {
00755       /*
00756        * Config changed since last browse.
00757        *
00758        * First try cheap last browse search. (Rebrowsing a different
00759        * previous category?)
00760        */
00761       for (cat = config->root; cat; cat = cat->next) {
00762          if (cat->name == prev) {
00763             /* Found it. */
00764             cat = cat->next;
00765             break;
00766          }
00767       }
00768       if (!cat) {
00769          /*
00770           * Have to do it the hard way. (Last category was deleted and
00771           * re-added?)
00772           */
00773          for (cat = config->root; cat; cat = cat->next) {
00774             if (!strcasecmp(cat->name, prev)) {
00775                /* Found it. */
00776                cat = cat->next;
00777                break;
00778             }
00779          }
00780       }
00781    }
00782    
00783    if (cat)
00784       cat = next_available_category(cat);
00785 
00786    config->last_browse = cat;
00787    return (cat) ? cat->name : NULL;
00788 }
00789 
00790 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
00791 {
00792    struct ast_variable *v;
00793 
00794    v = cat->root;
00795    cat->root = NULL;
00796    cat->last = NULL;
00797 
00798    return v;
00799 }
00800 
00801 void ast_category_rename(struct ast_category *cat, const char *name)
00802 {
00803    ast_copy_string(cat->name, name, sizeof(cat->name));
00804 }
00805 
00806 static void inherit_category(struct ast_category *new, const struct ast_category *base)
00807 {
00808    struct ast_variable *var;
00809    struct ast_category_template_instance *x;
00810 
00811    x = ast_calloc(1, sizeof(*x));
00812    if (!x) {
00813       return;
00814    }
00815    strcpy(x->name, base->name);
00816    x->inst = base;
00817    AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
00818    for (var = base->root; var; var = var->next)
00819       ast_variable_append(new, variable_clone(var));
00820 }
00821 
00822 struct ast_config *ast_config_new(void) 
00823 {
00824    struct ast_config *config;
00825 
00826    if ((config = ast_calloc(1, sizeof(*config))))
00827       config->max_include_level = MAX_INCLUDE_LEVEL;
00828    return config;
00829 }
00830 
00831 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
00832 {
00833    struct ast_variable *cur, *prev=NULL, *curn;
00834    int res = -1;
00835    int num_item = 0;
00836    int req_item;
00837 
00838    req_item = -1;
00839    if (!ast_strlen_zero(line)) {
00840       /* Requesting to delete by item number. */
00841       if (sscanf(line, "%30d", &req_item) != 1
00842          || req_item < 0) {
00843          /* Invalid item number to delete. */
00844          return -1;
00845       }
00846    }
00847 
00848    prev = NULL;
00849    cur = category->root;
00850    while (cur) {
00851       curn = cur->next;
00852       /* Delete by item number or by variable name with optional value. */
00853       if ((0 <= req_item && num_item == req_item)
00854          || (req_item < 0 && !strcasecmp(cur->name, variable)
00855             && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
00856          if (prev) {
00857             prev->next = cur->next;
00858             if (cur == category->last)
00859                category->last = prev;
00860          } else {
00861             category->root = cur->next;
00862             if (cur == category->last)
00863                category->last = NULL;
00864          }
00865          ast_variable_destroy(cur);
00866          res = 0;
00867       } else
00868          prev = cur;
00869 
00870       cur = curn;
00871       ++num_item;
00872    }
00873    return res;
00874 }
00875 
00876 int ast_variable_update(struct ast_category *category, const char *variable, 
00877                   const char *value, const char *match, unsigned int object)
00878 {
00879    struct ast_variable *cur, *prev=NULL, *newer=NULL;
00880 
00881    for (cur = category->root; cur; prev = cur, cur = cur->next) {
00882       if (strcasecmp(cur->name, variable) ||
00883          (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
00884          continue;
00885 
00886       if (!(newer = ast_variable_new(variable, value, cur->file)))
00887          return -1;
00888 
00889       ast_variable_move(newer, cur);
00890       newer->object = newer->object || object;
00891 
00892       /* Replace the old node in the list with the new node. */
00893       newer->next = cur->next;
00894       if (prev)
00895          prev->next = newer;
00896       else
00897          category->root = newer;
00898       if (category->last == cur)
00899          category->last = newer;
00900 
00901       ast_variable_destroy(cur);
00902 
00903       return 0;
00904    }
00905 
00906    /* Could not find variable to update */
00907    return -1;
00908 }
00909 
00910 int ast_category_delete(struct ast_config *cfg, const char *category)
00911 {
00912    struct ast_category *prev=NULL, *cat;
00913 
00914    cat = cfg->root;
00915    while (cat) {
00916       if (cat->name == category) {
00917          if (prev) {
00918             prev->next = cat->next;
00919             if (cat == cfg->last)
00920                cfg->last = prev;
00921          } else {
00922             cfg->root = cat->next;
00923             if (cat == cfg->last)
00924                cfg->last = NULL;
00925          }
00926          ast_category_destroy(cat);
00927          return 0;
00928       }
00929       prev = cat;
00930       cat = cat->next;
00931    }
00932 
00933    prev = NULL;
00934    cat = cfg->root;
00935    while (cat) {
00936       if (!strcasecmp(cat->name, category)) {
00937          if (prev) {
00938             prev->next = cat->next;
00939             if (cat == cfg->last)
00940                cfg->last = prev;
00941          } else {
00942             cfg->root = cat->next;
00943             if (cat == cfg->last)
00944                cfg->last = NULL;
00945          }
00946          ast_category_destroy(cat);
00947          return 0;
00948       }
00949       prev = cat;
00950       cat = cat->next;
00951    }
00952    return -1;
00953 }
00954 
00955 int ast_category_empty(struct ast_config *cfg, const char *category)
00956 {
00957    struct ast_category *cat;
00958 
00959    for (cat = cfg->root; cat; cat = cat->next) {
00960       if (!strcasecmp(cat->name, category))
00961          continue;
00962       ast_variables_destroy(cat->root);
00963       cat->root = NULL;
00964       cat->last = NULL;
00965       return 0;
00966    }
00967 
00968    return -1;
00969 }
00970 
00971 void ast_config_destroy(struct ast_config *cfg)
00972 {
00973    struct ast_category *cat, *catn;
00974 
00975    if (!cfg)
00976       return;
00977 
00978    ast_includes_destroy(cfg->includes);
00979 
00980    cat = cfg->root;
00981    while (cat) {
00982       catn = cat;
00983       cat = cat->next;
00984       ast_category_destroy(catn);
00985    }
00986    ast_free(cfg);
00987 }
00988 
00989 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
00990 {
00991    return cfg->current;
00992 }
00993 
00994 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
00995 {
00996    /* cast below is just to silence compiler warning about dropping "const" */
00997    cfg->current = (struct ast_category *) cat;
00998 }
00999 
01000 /*!
01001  * \internal
01002  * \brief Create a new cfmtime list node.
01003  *
01004  * \param filename Config filename caching.
01005  * \param who_asked Who wanted to know.
01006  *
01007  * \retval cfmtime New node on success.
01008  * \retval NULL on error.
01009  */
01010 static struct cache_file_mtime *cfmtime_new(const char *filename, const char *who_asked)
01011 {
01012    struct cache_file_mtime *cfmtime;
01013    char *dst;
01014 
01015    cfmtime = ast_calloc(1,
01016       sizeof(*cfmtime) + strlen(filename) + 1 + strlen(who_asked) + 1);
01017    if (!cfmtime) {
01018       return NULL;
01019    }
01020    dst = cfmtime->filename;   /* writable space starts here */
01021    strcpy(dst, filename);
01022    dst += strlen(dst) + 1;
01023    cfmtime->who_asked = strcpy(dst, who_asked);
01024 
01025    return cfmtime;
01026 }
01027 
01028 enum config_cache_attribute_enum {
01029    ATTRIBUTE_INCLUDE = 0,
01030    ATTRIBUTE_EXEC = 1,
01031 };
01032 
01033 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
01034 {
01035    struct cache_file_mtime *cfmtime;
01036    struct cache_file_include *cfinclude;
01037    struct stat statbuf = { 0, };
01038 
01039    /* Find our cached entry for this configuration file */
01040    AST_LIST_LOCK(&cfmtime_head);
01041    AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
01042       if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
01043          break;
01044    }
01045    if (!cfmtime) {
01046       cfmtime = cfmtime_new(configfile, who_asked);
01047       if (!cfmtime) {
01048          AST_LIST_UNLOCK(&cfmtime_head);
01049          return;
01050       }
01051       /* Note that the file mtime is initialized to 0, i.e. 1970 */
01052       AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
01053    }
01054 
01055    if (!stat(configfile, &statbuf))
01056       cfmtime->mtime = 0;
01057    else
01058       cfmtime->mtime = statbuf.st_mtime;
01059 
01060    switch (attrtype) {
01061    case ATTRIBUTE_INCLUDE:
01062       AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
01063          if (!strcmp(cfinclude->include, filename)) {
01064             AST_LIST_UNLOCK(&cfmtime_head);
01065             return;
01066          }
01067       }
01068       cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
01069       if (!cfinclude) {
01070          AST_LIST_UNLOCK(&cfmtime_head);
01071          return;
01072       }
01073       strcpy(cfinclude->include, filename);
01074       AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
01075       break;
01076    case ATTRIBUTE_EXEC:
01077       cfmtime->has_exec = 1;
01078       break;
01079    }
01080    AST_LIST_UNLOCK(&cfmtime_head);
01081 }
01082 
01083 /*! \brief parse one line in the configuration.
01084  * \verbatim
01085  * We can have a category header [foo](...)
01086  * a directive          #include / #exec
01087  * or a regular line       name = value
01088  * \endverbatim
01089  */
01090 static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
01091    char *buf, int lineno, const char *configfile, struct ast_flags flags,
01092    struct ast_str *comment_buffer,
01093    struct ast_str *lline_buffer,
01094    const char *suggested_include_file,
01095    struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
01096 {
01097    char *c;
01098    char *cur = buf;
01099    struct ast_variable *v;
01100    char cmd[512], exec_file[512];
01101 
01102    /* Actually parse the entry */
01103    if (cur[0] == '[') { /* A category header */
01104       /* format is one of the following:
01105        * [foo] define a new category named 'foo'
01106        * [foo](!) define a new template category named 'foo'
01107        * [foo](+) append to category 'foo', error if foo does not exist.
01108        * [foo](a) define a new category and inherit from template a.
01109        *    You can put a comma-separated list of templates and '!' and '+'
01110        *    between parentheses, with obvious meaning.
01111        */
01112       struct ast_category *newcat = NULL;
01113       char *catname;
01114 
01115       c = strchr(cur, ']');
01116       if (!c) {
01117          ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
01118          return -1;
01119       }
01120       *c++ = '\0';
01121       cur++;
01122       if (*c++ != '(')
01123          c = NULL;
01124       catname = cur;
01125       if (!(*cat = newcat = ast_category_new(catname,
01126             S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
01127             lineno))) {
01128          return -1;
01129       }
01130       (*cat)->lineno = lineno;
01131       *last_var = 0;
01132       *last_cat = newcat;
01133       
01134       /* add comments */
01135       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01136          newcat->precomments = ALLOC_COMMENT(comment_buffer);
01137       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01138          newcat->sameline = ALLOC_COMMENT(lline_buffer);
01139       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01140          CB_RESET(comment_buffer, lline_buffer);
01141       
01142       /* If there are options or categories to inherit from, process them now */
01143       if (c) {
01144          if (!(cur = strchr(c, ')'))) {
01145             ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
01146             return -1;
01147          }
01148          *cur = '\0';
01149          while ((cur = strsep(&c, ","))) {
01150             if (!strcasecmp(cur, "!")) {
01151                (*cat)->ignored = 1;
01152             } else if (!strcasecmp(cur, "+")) {
01153                *cat = category_get(cfg, catname, 1);
01154                if (!(*cat)) {
01155                   if (newcat)
01156                      ast_category_destroy(newcat);
01157                   ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
01158                   return -1;
01159                }
01160                if (newcat) {
01161                   move_variables(newcat, *cat);
01162                   ast_category_destroy(newcat);
01163                   newcat = NULL;
01164                }
01165             } else {
01166                struct ast_category *base;
01167             
01168                base = category_get(cfg, cur, 1);
01169                if (!base) {
01170                   ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
01171                   return -1;
01172                }
01173                inherit_category(*cat, base);
01174             }
01175          }
01176       }
01177       if (newcat)
01178          ast_category_append(cfg, *cat);
01179    } else if (cur[0] == '#') { /* A directive - #include or #exec */
01180       char *cur2;
01181       char real_inclusion_name[256];
01182       int do_include = 0;  /* otherwise, it is exec */
01183 
01184       cur++;
01185       c = cur;
01186       while (*c && (*c > 32)) {
01187          c++;
01188       }
01189 
01190       if (*c) {
01191          *c = '\0';
01192          /* Find real argument */
01193          c = ast_strip(c + 1);
01194          if (!(*c)) {
01195             c = NULL;
01196          }
01197       } else {
01198          c = NULL;
01199       }
01200       if (!strcasecmp(cur, "include")) {
01201          do_include = 1;
01202       } else if (!strcasecmp(cur, "exec")) {
01203          if (!ast_opt_exec_includes) {
01204             ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
01205             return 0;   /* XXX is this correct ? or we should return -1 ? */
01206          }
01207       } else {
01208          ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
01209          return 0;   /* XXX is this correct ? or we should return -1 ? */
01210       }
01211 
01212       if (c == NULL) {
01213          ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n", 
01214                do_include ? "include" : "exec",
01215                do_include ? "filename" : "/path/to/executable",
01216                lineno,
01217                configfile);
01218          return 0;   /* XXX is this correct ? or we should return -1 ? */
01219       }
01220 
01221       cur = c;
01222       /* Strip off leading and trailing "'s and <>'s */
01223       /* Dequote */
01224       if ((*c == '"') || (*c == '<')) {
01225          char quote_char = *c;
01226          if (quote_char == '<') {
01227             quote_char = '>';
01228          }
01229 
01230          if (*(c + strlen(c) - 1) == quote_char) {
01231             cur++;
01232             *(c + strlen(c) - 1) = '\0';
01233          }
01234       }
01235       cur2 = cur;
01236 
01237       /* #exec </path/to/executable>
01238          We create a tmp file, then we #include it, then we delete it. */
01239       if (!do_include) {
01240          struct timeval now = ast_tvnow();
01241          if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
01242             config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
01243          snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
01244          snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
01245          ast_safe_system(cmd);
01246          cur = exec_file;
01247       } else {
01248          if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
01249             config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
01250          exec_file[0] = '\0';
01251       }
01252       /* A #include */
01253       /* record this inclusion */
01254       ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
01255 
01256       do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
01257       if (!ast_strlen_zero(exec_file))
01258          unlink(exec_file);
01259       if (!do_include) {
01260          ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
01261          return -1;
01262       }
01263       /* XXX otherwise what ? the default return is 0 anyways */
01264 
01265    } else {
01266       /* Just a line (variable = value) */
01267       int object = 0;
01268       if (!(*cat)) {
01269          ast_log(LOG_WARNING,
01270             "parse error: No category context for line %d of %s\n", lineno, configfile);
01271          return -1;
01272       }
01273       c = strchr(cur, '=');
01274 
01275       if (c && c > cur && (*(c - 1) == '+')) {
01276          struct ast_variable *var, *replace = NULL;
01277          struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
01278 
01279          if (!str || !*str) {
01280             return -1;
01281          }
01282 
01283          *(c - 1) = '\0';
01284          c++;
01285          cur = ast_strip(cur);
01286 
01287          /* Must iterate through category until we find last variable of same name (since there could be multiple) */
01288          for (var = ast_category_first(*cat); var; var = var->next) {
01289             if (!strcmp(var->name, cur)) {
01290                replace = var;
01291             }
01292          }
01293 
01294          if (!replace) {
01295             /* Nothing to replace; just set a variable normally. */
01296             goto set_new_variable;
01297          }
01298 
01299          ast_str_set(str, 0, "%s", replace->value);
01300          ast_str_append(str, 0, "%s", c);
01301          ast_str_trim_blanks(*str);
01302          ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
01303       } else if (c) {
01304          *c = 0;
01305          c++;
01306          /* Ignore > in => */
01307          if (*c== '>') {
01308             object = 1;
01309             c++;
01310          }
01311 set_new_variable:
01312          if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
01313             v->lineno = lineno;
01314             v->object = object;
01315             *last_cat = 0;
01316             *last_var = v;
01317             /* Put and reset comments */
01318             v->blanklines = 0;
01319             ast_variable_append(*cat, v);
01320             /* add comments */
01321             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01322                v->precomments = ALLOC_COMMENT(comment_buffer);
01323             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01324                v->sameline = ALLOC_COMMENT(lline_buffer);
01325             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01326                CB_RESET(comment_buffer, lline_buffer);
01327             
01328          } else {
01329             return -1;
01330          }
01331       } else {
01332          ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
01333       }
01334    }
01335    return 0;
01336 }
01337 
01338 static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
01339 {
01340    char fn[256];
01341 #if defined(LOW_MEMORY)
01342    char buf[512];
01343 #else
01344    char buf[8192];
01345 #endif
01346    char *new_buf, *comment_p, *process_buf;
01347    FILE *f;
01348    int lineno=0;
01349    int comment = 0, nest[MAX_NESTED_COMMENTS];
01350    struct ast_category *cat = NULL;
01351    int count = 0;
01352    struct stat statbuf;
01353    struct cache_file_mtime *cfmtime = NULL;
01354    struct cache_file_include *cfinclude;
01355    struct ast_variable *last_var = 0;
01356    struct ast_category *last_cat = 0;
01357    /*! Growable string buffer */
01358    struct ast_str *comment_buffer = NULL; /*!< this will be a comment collector.*/
01359    struct ast_str *lline_buffer = NULL;   /*!< A buffer for stuff behind the ; */
01360 
01361    if (cfg)
01362       cat = ast_config_get_current_category(cfg);
01363 
01364    if (filename[0] == '/') {
01365       ast_copy_string(fn, filename, sizeof(fn));
01366    } else {
01367       snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
01368    }
01369 
01370    if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01371       comment_buffer = ast_str_create(CB_SIZE);
01372       if (comment_buffer)
01373          lline_buffer = ast_str_create(CB_SIZE);
01374       if (!lline_buffer) {
01375          ast_free(comment_buffer);
01376          ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
01377          return NULL;
01378       }
01379    }
01380 #ifdef AST_INCLUDE_GLOB
01381    {
01382       int glob_ret;
01383       glob_t globbuf;
01384       globbuf.gl_offs = 0; /* initialize it to silence gcc */
01385       glob_ret = glob(fn, MY_GLOB_FLAGS, NULL, &globbuf);
01386       if (glob_ret == GLOB_NOSPACE)
01387          ast_log(LOG_WARNING,
01388             "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
01389       else if (glob_ret  == GLOB_ABORTED)
01390          ast_log(LOG_WARNING,
01391             "Glob Expansion of pattern '%s' failed: Read error\n", fn);
01392       else  {
01393          /* loop over expanded files */
01394          int i;
01395          for (i=0; i<globbuf.gl_pathc; i++) {
01396             ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
01397 #endif
01398    /*
01399     * The following is not a loop, but just a convenient way to define a block
01400     * (using do { } while(0) ), and be able to exit from it with 'continue'
01401     * or 'break' in case of errors. Nice trick.
01402     */
01403    do {
01404       if (stat(fn, &statbuf))
01405          continue;
01406 
01407       if (!S_ISREG(statbuf.st_mode)) {
01408          ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
01409          continue;
01410       }
01411 
01412       if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
01413          /* Find our cached entry for this configuration file */
01414          AST_LIST_LOCK(&cfmtime_head);
01415          AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
01416             if (!strcmp(cfmtime->filename, fn) && !strcmp(cfmtime->who_asked, who_asked))
01417                break;
01418          }
01419          if (!cfmtime) {
01420             cfmtime = cfmtime_new(fn, who_asked);
01421             if (!cfmtime)
01422                continue;
01423             /* Note that the file mtime is initialized to 0, i.e. 1970 */
01424             AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
01425          }
01426       }
01427 
01428       if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
01429          /* File is unchanged, what about the (cached) includes (if any)? */
01430          int unchanged = 1;
01431          AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
01432             /* We must glob here, because if we did not, then adding a file to globbed directory would
01433              * incorrectly cause no reload to be necessary. */
01434             char fn2[256];
01435 #ifdef AST_INCLUDE_GLOB
01436             int glob_return;
01437             glob_t glob_buf = { .gl_offs = 0 };
01438             glob_return = glob(cfinclude->include, MY_GLOB_FLAGS, NULL, &glob_buf);
01439             /* On error, we reparse */
01440             if (glob_return == GLOB_NOSPACE || glob_return  == GLOB_ABORTED)
01441                unchanged = 0;
01442             else  {
01443                /* loop over expanded files */
01444                int j;
01445                for (j = 0; j < glob_buf.gl_pathc; j++) {
01446                   ast_copy_string(fn2, glob_buf.gl_pathv[j], sizeof(fn2));
01447 #else
01448                   ast_copy_string(fn2, cfinclude->include);
01449 #endif
01450                   if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "", who_asked) == NULL) {
01451                      /* that second-to-last field needs to be looked at in this case... TODO */
01452                      unchanged = 0;
01453                      /* One change is enough to short-circuit and reload the whole shebang */
01454                      break;
01455                   }
01456 #ifdef AST_INCLUDE_GLOB
01457                }
01458             }
01459 #endif
01460          }
01461 
01462          if (unchanged) {
01463             AST_LIST_UNLOCK(&cfmtime_head);
01464             return CONFIG_STATUS_FILEUNCHANGED;
01465          }
01466       }
01467       if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
01468          AST_LIST_UNLOCK(&cfmtime_head);
01469 
01470       /* If cfg is NULL, then we just want an answer */
01471       if (cfg == NULL) {
01472          ast_free(comment_buffer);
01473          ast_free(lline_buffer);
01474          return NULL;
01475       }
01476 
01477       if (cfmtime)
01478          cfmtime->mtime = statbuf.st_mtime;
01479 
01480       ast_verb(2, "Parsing '%s': ", fn);
01481          fflush(stdout);
01482       if (!(f = fopen(fn, "r"))) {
01483          ast_debug(1, "No file to parse: %s\n", fn);
01484          ast_verb(2, "Not found (%s)\n", strerror(errno));
01485          continue;
01486       }
01487       count++;
01488       /* If we get to this point, then we're loading regardless */
01489       ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
01490       ast_debug(1, "Parsing %s\n", fn);
01491       ast_verb(2, "Found\n");
01492       while (!feof(f)) {
01493          lineno++;
01494          if (fgets(buf, sizeof(buf), f)) {
01495             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && lline_buffer && ast_str_strlen(lline_buffer)) {
01496                CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));       /* add the current lline buffer to the comment buffer */
01497                ast_str_reset(lline_buffer);        /* erase the lline buffer */
01498             }
01499             
01500             new_buf = buf;
01501             if (comment) 
01502                process_buf = NULL;
01503             else
01504                process_buf = buf;
01505             
01506             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer) && (ast_strlen_zero(buf) || strlen(buf) == strspn(buf," \t\n\r"))) {
01507                /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
01508                CB_ADD(&comment_buffer, "\n");       /* add a newline to the comment buffer */
01509                continue; /* go get a new line, then */
01510             }
01511             
01512             while ((comment_p = strchr(new_buf, COMMENT_META))) {
01513                if ((comment_p > new_buf) && (*(comment_p - 1) == '\\')) {
01514                   /* Escaped semicolons aren't comments. */
01515                   new_buf = comment_p + 1;
01516                } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
01517                   /* Meta-Comment start detected ";--" */
01518                   if (comment < MAX_NESTED_COMMENTS) {
01519                      *comment_p = '\0';
01520                      new_buf = comment_p + 3;
01521                      comment++;
01522                      nest[comment-1] = lineno;
01523                   } else {
01524                      ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
01525                   }
01526                } else if ((comment_p >= new_buf + 2) &&
01527                      (*(comment_p - 1) == COMMENT_TAG) &&
01528                      (*(comment_p - 2) == COMMENT_TAG)) {
01529                   /* Meta-Comment end detected */
01530                   comment--;
01531                   new_buf = comment_p + 1;
01532                   if (!comment) {
01533                      /* Back to non-comment now */
01534                      if (process_buf) {
01535                         /* Actually have to move what's left over the top, then continue */
01536                         char *oldptr;
01537                         oldptr = process_buf + strlen(process_buf);
01538                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01539                            CB_ADD(&comment_buffer, ";");
01540                            CB_ADD_LEN(&comment_buffer, oldptr+1, new_buf-oldptr-1);
01541                         }
01542                         
01543                         memmove(oldptr, new_buf, strlen(new_buf) + 1);
01544                         new_buf = oldptr;
01545                      } else
01546                         process_buf = new_buf;
01547                   }
01548                } else {
01549                   if (!comment) {
01550                      /* If ; is found, and we are not nested in a comment, 
01551                         we immediately stop all comment processing */
01552                      if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01553                         CB_ADD(&lline_buffer, comment_p);
01554                      }
01555                      *comment_p = '\0'; 
01556                      new_buf = comment_p;
01557                   } else
01558                      new_buf = comment_p + 1;
01559                }
01560             }
01561             if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf ) {
01562                CB_ADD(&comment_buffer, buf);  /* the whole line is a comment, store it */
01563             }
01564             
01565             if (process_buf) {
01566                char *buffer = ast_strip(process_buf);
01567                if (!ast_strlen_zero(buffer)) {
01568                   if (process_text_line(cfg, &cat, buffer, lineno, fn, flags, comment_buffer, lline_buffer, suggested_include_file, &last_cat, &last_var, who_asked)) {
01569                      cfg = CONFIG_STATUS_FILEINVALID;
01570                      break;
01571                   }
01572                }
01573             }
01574          }
01575       }
01576       /* end of file-- anything in a comment buffer? */
01577       if (last_cat) {
01578          if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01579             if (lline_buffer && ast_str_strlen(lline_buffer)) {
01580                CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));       /* add the current lline buffer to the comment buffer */
01581                ast_str_reset(lline_buffer);        /* erase the lline buffer */
01582             }
01583             last_cat->trailing = ALLOC_COMMENT(comment_buffer);
01584          }
01585       } else if (last_var) {
01586          if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01587             if (lline_buffer && ast_str_strlen(lline_buffer)) {
01588                CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));       /* add the current lline buffer to the comment buffer */
01589                ast_str_reset(lline_buffer);        /* erase the lline buffer */
01590             }
01591             last_var->trailing = ALLOC_COMMENT(comment_buffer);
01592          }
01593       } else {
01594          if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01595             ast_debug(1, "Nothing to attach comments to, discarded: %s\n", ast_str_buffer(comment_buffer));
01596          }
01597       }
01598       if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01599          CB_RESET(comment_buffer, lline_buffer);
01600 
01601       fclose(f);
01602    } while (0);
01603    if (comment) {
01604       ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
01605    }
01606 #ifdef AST_INCLUDE_GLOB
01607                if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
01608                   break;
01609                }
01610             }
01611             globfree(&globbuf);
01612          }
01613       }
01614 #endif
01615 
01616    if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01617       ast_free(comment_buffer);
01618       ast_free(lline_buffer);
01619       comment_buffer = NULL;
01620       lline_buffer = NULL;
01621    }
01622    
01623    if (count == 0)
01624       return NULL;
01625 
01626    return cfg;
01627 }
01628 
01629 
01630 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
01631    which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
01632    recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
01633    be shocked and mystified as to why things are not showing up in the files! 
01634 
01635    Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
01636    and line number are stored for each include, plus the name of the file included, so that these statements may be
01637    included in the output files on a file_save operation. 
01638 
01639    The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
01640    are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
01641    the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
01642    and a header gets added.
01643 
01644    vars and category heads are output in the order they are stored in the config file. So, if the software
01645    shuffles these at all, then the placement of #include directives might get a little mixed up, because the
01646    file/lineno data probably won't get changed.
01647 
01648 */
01649 
01650 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
01651 {
01652    char date[256]="";
01653    time_t t;
01654 
01655    time(&t);
01656    ast_copy_string(date, ctime(&t), sizeof(date));
01657 
01658    fprintf(f1, ";!\n");
01659    fprintf(f1, ";! Automatically generated configuration file\n");
01660    if (strcmp(configfile, fn))
01661       fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
01662    else
01663       fprintf(f1, ";! Filename: %s\n", configfile);
01664    fprintf(f1, ";! Generator: %s\n", generator);
01665    fprintf(f1, ";! Creation Date: %s", date);
01666    fprintf(f1, ";!\n");
01667 }
01668 
01669 static void inclfile_destroy(void *obj)
01670 {
01671    const struct inclfile *o = obj;
01672 
01673    ast_free(o->fname);
01674 }
01675 
01676 
01677 static struct inclfile *set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset)
01678 {
01679    struct inclfile lookup;
01680    struct inclfile *fi;
01681 
01682    if (ast_strlen_zero(file)) {
01683       if (configfile[0] == '/')
01684          ast_copy_string(fn, configfile, fn_size);
01685       else
01686          snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
01687    } else if (file[0] == '/')
01688       ast_copy_string(fn, file, fn_size);
01689    else
01690       snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
01691    lookup.fname = fn;
01692    fi = ao2_find(fileset, &lookup, OBJ_POINTER);
01693    if (fi) {
01694       /* Found existing include file scratch pad. */
01695       return fi;
01696    }
01697 
01698    /* set up a file scratch pad */
01699    fi = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
01700    if (!fi) {
01701       /* Scratch pad creation failed. */
01702       return NULL;
01703    }
01704    fi->fname = ast_strdup(fn);
01705    if (!fi->fname) {
01706       /* Scratch pad creation failed. */
01707       ao2_ref(fi, -1);
01708       return NULL;
01709    }
01710    fi->lineno = 1;
01711 
01712    ao2_link(fileset, fi);
01713 
01714    return fi;
01715 }
01716 
01717 static int count_linefeeds(char *str)
01718 {
01719    int count = 0;
01720 
01721    while (*str) {
01722       if (*str =='\n')
01723          count++;
01724       str++;
01725    }
01726    return count;
01727 }
01728 
01729 static int count_linefeeds_in_comments(struct ast_comment *x)
01730 {
01731    int count = 0;
01732 
01733    while (x) {
01734       count += count_linefeeds(x->cmt);
01735       x = x->next;
01736    }
01737    return count;
01738 }
01739 
01740 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
01741 {
01742    int precomment_lines;
01743    int i;
01744 
01745    if (!fi) {
01746       /* No file scratch pad object so insert no blank lines. */
01747       return;
01748    }
01749 
01750    precomment_lines = count_linefeeds_in_comments(precomments);
01751 
01752    /* I don't have to worry about those ;! comments, they are
01753       stored in the precomments, but not printed back out.
01754       I did have to make sure that comments following
01755       the ;! header comments were not also deleted in the process */
01756    if (lineno - precomment_lines - fi->lineno < 0) { /* insertions can mess up the line numbering and produce negative numbers that mess things up */
01757       return;
01758    } else if (lineno == 0) {
01759       /* Line replacements also mess things up */
01760       return;
01761    } else if (lineno - precomment_lines - fi->lineno < 5) {
01762       /* Only insert less than 5 blank lines; if anything more occurs,
01763        * it's probably due to context deletion. */
01764       for (i = fi->lineno; i < lineno - precomment_lines; i++) {
01765          fprintf(fp, "\n");
01766       }
01767    } else {
01768       /* Deletion occurred - insert a single blank line, for separation of
01769        * contexts. */
01770       fprintf(fp, "\n");
01771    }
01772  
01773    fi->lineno = lineno + 1; /* Advance the file lineno */
01774 }
01775 
01776 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
01777 {
01778    return ast_config_text_file_save(configfile, cfg, generator);
01779 }
01780 
01781 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
01782 {
01783    FILE *f;
01784    char fn[PATH_MAX];
01785    struct ast_variable *var;
01786    struct ast_category *cat;
01787    struct ast_comment *cmt;
01788    struct ast_config_include *incl;
01789    int blanklines = 0;
01790    struct ao2_container *fileset;
01791    struct inclfile *fi;
01792 
01793    fileset = ao2_container_alloc(1023, hash_string, hashtab_compare_strings);
01794    if (!fileset) {
01795       /* Container creation failed. */
01796       return -1;
01797    }
01798 
01799    /* reset all the output flags, in case this isn't our first time saving this data */
01800    for (incl = cfg->includes; incl; incl = incl->next) {
01801       incl->output = 0;
01802    }
01803 
01804    /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
01805       are all truncated to zero bytes and have that nice header*/
01806    for (incl = cfg->includes; incl; incl = incl->next) {
01807       if (!incl->exec) { /* leave the execs alone -- we'll write out the #exec directives, but won't zero out the include files or exec files*/
01808          /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
01809          fi = set_fn(fn, sizeof(fn), incl->included_file, configfile, fileset);
01810          f = fopen(fn, "w");
01811          if (f) {
01812             gen_header(f, configfile, fn, generator);
01813             fclose(f); /* this should zero out the file */
01814          } else {
01815             ast_debug(1, "Unable to open for writing: %s\n", fn);
01816             ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
01817          }
01818          if (fi) {
01819             ao2_ref(fi, -1);
01820          }
01821       }
01822    }
01823 
01824    /* just set fn to absolute ver of configfile */
01825    fi = set_fn(fn, sizeof(fn), 0, configfile, fileset);
01826    if (
01827 #ifdef __CYGWIN__
01828       (f = fopen(fn, "w+"))
01829 #else
01830       (f = fopen(fn, "w"))
01831 #endif
01832       ) {
01833       ast_verb(2, "Saving '%s': ", fn);
01834       gen_header(f, configfile, fn, generator);
01835       cat = cfg->root;
01836       fclose(f);
01837       if (fi) {
01838          ao2_ref(fi, -1);
01839       }
01840 
01841       /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
01842       /* since each var, cat, and associated comments can come from any file, we have to be
01843          mobile, and open each file, print, and close it on an entry-by-entry basis */
01844 
01845       while (cat) {
01846          fi = set_fn(fn, sizeof(fn), cat->file, configfile, fileset);
01847          f = fopen(fn, "a");
01848          if (!f) {
01849             ast_debug(1, "Unable to open for writing: %s\n", fn);
01850             ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
01851             if (fi) {
01852                ao2_ref(fi, -1);
01853             }
01854             ao2_ref(fileset, -1);
01855             return -1;
01856          }
01857 
01858          /* dump any includes that happen before this category header */
01859          for (incl=cfg->includes; incl; incl = incl->next) {
01860             if (strcmp(incl->include_location_file, cat->file) == 0){
01861                if (cat->lineno > incl->include_location_lineno && !incl->output) {
01862                   if (incl->exec)
01863                      fprintf(f,"#exec \"%s\"\n", incl->exec_file);
01864                   else
01865                      fprintf(f,"#include \"%s\"\n", incl->included_file);
01866                   incl->output = 1;
01867                }
01868             }
01869          }
01870 
01871          insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
01872          /* Dump section with any appropriate comment */
01873          for (cmt = cat->precomments; cmt; cmt=cmt->next) {
01874             char *cmtp = cmt->cmt;
01875             while (*cmtp == ';' && *(cmtp+1) == '!') {
01876                char *cmtp2 = strchr(cmtp+1, '\n');
01877                if (cmtp2)
01878                   cmtp = cmtp2+1;
01879                else cmtp = 0;
01880             }
01881             if (cmtp)
01882                fprintf(f,"%s", cmtp);
01883          }
01884          fprintf(f, "[%s]", cat->name);
01885          if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
01886             fprintf(f, "(");
01887             if (cat->ignored) {
01888                fprintf(f, "!");
01889             }
01890             if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
01891                fprintf(f, ",");
01892             }
01893             if (!AST_LIST_EMPTY(&cat->template_instances)) {
01894                struct ast_category_template_instance *x;
01895                AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
01896                   fprintf(f,"%s",x->name);
01897                   if (x != AST_LIST_LAST(&cat->template_instances))
01898                      fprintf(f,",");
01899                }
01900             }
01901             fprintf(f, ")");
01902          }
01903          for(cmt = cat->sameline; cmt; cmt=cmt->next)
01904          {
01905             fprintf(f,"%s", cmt->cmt);
01906          }
01907          if (!cat->sameline)
01908             fprintf(f,"\n");
01909          for (cmt = cat->trailing; cmt; cmt=cmt->next) {
01910             if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
01911                fprintf(f,"%s", cmt->cmt);
01912          }
01913          fclose(f);
01914          if (fi) {
01915             ao2_ref(fi, -1);
01916          }
01917 
01918          var = cat->root;
01919          while (var) {
01920             struct ast_category_template_instance *x;
01921             int found = 0;
01922             AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
01923                struct ast_variable *v;
01924                for (v = x->inst->root; v; v = v->next) {
01925                   if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
01926                      found = 1;
01927                      break;
01928                   }
01929                }
01930                if (found)
01931                   break;
01932             }
01933             if (found) {
01934                var = var->next;
01935                continue;
01936             }
01937             fi = set_fn(fn, sizeof(fn), var->file, configfile, fileset);
01938             f = fopen(fn, "a");
01939             if (!f) {
01940                ast_debug(1, "Unable to open for writing: %s\n", fn);
01941                ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
01942                if (fi) {
01943                   ao2_ref(fi, -1);
01944                }
01945                ao2_ref(fileset, -1);
01946                return -1;
01947             }
01948 
01949             /* dump any includes that happen before this category header */
01950             for (incl=cfg->includes; incl; incl = incl->next) {
01951                if (strcmp(incl->include_location_file, var->file) == 0){
01952                   if (var->lineno > incl->include_location_lineno && !incl->output) {
01953                      if (incl->exec)
01954                         fprintf(f,"#exec \"%s\"\n", incl->exec_file);
01955                      else
01956                         fprintf(f,"#include \"%s\"\n", incl->included_file);
01957                      incl->output = 1;
01958                   }
01959                }
01960             }
01961 
01962             insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
01963             for (cmt = var->precomments; cmt; cmt=cmt->next) {
01964                if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
01965                   fprintf(f,"%s", cmt->cmt);
01966             }
01967             if (var->sameline)
01968                fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
01969             else
01970                fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
01971             for (cmt = var->trailing; cmt; cmt=cmt->next) {
01972                if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
01973                   fprintf(f,"%s", cmt->cmt);
01974             }
01975             if (var->blanklines) {
01976                blanklines = var->blanklines;
01977                while (blanklines--)
01978                   fprintf(f, "\n");
01979             }
01980 
01981             fclose(f);
01982             if (fi) {
01983                ao2_ref(fi, -1);
01984             }
01985 
01986             var = var->next;
01987          }
01988          cat = cat->next;
01989       }
01990       if (!option_debug)
01991          ast_verb(2, "Saved\n");
01992    } else {
01993       ast_debug(1, "Unable to open for writing: %s\n", fn);
01994       ast_verb(2, "Unable to write (%s)", strerror(errno));
01995       if (fi) {
01996          ao2_ref(fi, -1);
01997       }
01998       ao2_ref(fileset, -1);
01999       return -1;
02000    }
02001 
02002    /* Now, for files with trailing #include/#exec statements,
02003       we have to make sure every entry is output */
02004    for (incl=cfg->includes; incl; incl = incl->next) {
02005       if (!incl->output) {
02006          /* open the respective file */
02007          fi = set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset);
02008          f = fopen(fn, "a");
02009          if (!f) {
02010             ast_debug(1, "Unable to open for writing: %s\n", fn);
02011             ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
02012             if (fi) {
02013                ao2_ref(fi, -1);
02014             }
02015             ao2_ref(fileset, -1);
02016             return -1;
02017          }
02018 
02019          /* output the respective include */
02020          if (incl->exec)
02021             fprintf(f,"#exec \"%s\"\n", incl->exec_file);
02022          else
02023             fprintf(f,"#include \"%s\"\n", incl->included_file);
02024          fclose(f);
02025          incl->output = 1;
02026          if (fi) {
02027             ao2_ref(fi, -1);
02028          }
02029       }
02030    }
02031    ao2_ref(fileset, -1); /* this should destroy the hash container */
02032 
02033    return 0;
02034 }
02035 
02036 static void clear_config_maps(void) 
02037 {
02038    struct ast_config_map *map;
02039 
02040    ast_mutex_lock(&config_lock);
02041 
02042    while (config_maps) {
02043       map = config_maps;
02044       config_maps = config_maps->next;
02045       ast_free(map);
02046    }
02047       
02048    ast_mutex_unlock(&config_lock);
02049 }
02050 
02051 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
02052 {
02053    struct ast_config_map *map;
02054    char *dst;
02055    int length;
02056 
02057    length = sizeof(*map);
02058    length += strlen(name) + 1;
02059    length += strlen(driver) + 1;
02060    length += strlen(database) + 1;
02061    if (table)
02062       length += strlen(table) + 1;
02063 
02064    if (!(map = ast_calloc(1, length)))
02065       return -1;
02066 
02067    dst = map->stuff; /* writable space starts here */
02068    map->name = strcpy(dst, name);
02069    dst += strlen(dst) + 1;
02070    map->driver = strcpy(dst, driver);
02071    dst += strlen(dst) + 1;
02072    map->database = strcpy(dst, database);
02073    if (table) {
02074       dst += strlen(dst) + 1;
02075       map->table = strcpy(dst, table);
02076    }
02077    map->priority = priority;
02078    map->next = config_maps;
02079    config_maps = map;
02080 
02081    ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
02082 
02083    return 0;
02084 }
02085 
02086 int read_config_maps(void) 
02087 {
02088    struct ast_config *config, *configtmp;
02089    struct ast_variable *v;
02090    char *driver, *table, *database, *textpri, *stringp, *tmp;
02091    struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
02092    int pri;
02093 
02094    clear_config_maps();
02095 
02096    configtmp = ast_config_new();
02097    configtmp->max_include_level = 1;
02098    config = ast_config_internal_load(extconfig_conf, configtmp, flags, "", "extconfig");
02099    if (config == CONFIG_STATUS_FILEINVALID) {
02100       return -1;
02101    } else if (!config) {
02102       ast_config_destroy(configtmp);
02103       return 0;
02104    }
02105 
02106    for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
02107       char buf[512];
02108       ast_copy_string(buf, v->value, sizeof(buf));
02109       stringp = buf;
02110       driver = strsep(&stringp, ",");
02111 
02112       if ((tmp = strchr(stringp, '\"')))
02113          stringp = tmp;
02114 
02115       /* check if the database text starts with a double quote */
02116       if (*stringp == '"') {
02117          stringp++;
02118          database = strsep(&stringp, "\"");
02119          strsep(&stringp, ",");
02120       } else {
02121          /* apparently this text has no quotes */
02122          database = strsep(&stringp, ",");
02123       }
02124 
02125       table = strsep(&stringp, ",");
02126       textpri = strsep(&stringp, ",");
02127       if (!textpri || !(pri = atoi(textpri))) {
02128          pri = 1;
02129       }
02130 
02131       if (!strcmp(v->name, extconfig_conf)) {
02132          ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
02133          continue;
02134       }
02135 
02136       if (!strcmp(v->name, "asterisk.conf")) {
02137          ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
02138          continue;
02139       }
02140 
02141       if (!strcmp(v->name, "logger.conf")) {
02142          ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
02143          continue;
02144       }
02145 
02146       if (!driver || !database)
02147          continue;
02148       if (!strcasecmp(v->name, "sipfriends")) {
02149          ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sipusers and sippeers, though they can point to the same table.\n");
02150          append_mapping("sipusers", driver, database, table ? table : "sipfriends", pri);
02151          append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri);
02152       } else if (!strcasecmp(v->name, "iaxfriends")) {
02153          ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n");
02154          append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri);
02155          append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri);
02156       } else 
02157          append_mapping(v->name, driver, database, table, pri);
02158    }
02159       
02160    ast_config_destroy(config);
02161    return 0;
02162 }
02163 
02164 int ast_config_engine_register(struct ast_config_engine *new) 
02165 {
02166    struct ast_config_engine *ptr;
02167 
02168    ast_mutex_lock(&config_lock);
02169 
02170    if (!config_engine_list) {
02171       config_engine_list = new;
02172    } else {
02173       for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
02174       ptr->next = new;
02175    }
02176 
02177    ast_mutex_unlock(&config_lock);
02178    ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
02179 
02180    return 1;
02181 }
02182 
02183 int ast_config_engine_deregister(struct ast_config_engine *del) 
02184 {
02185    struct ast_config_engine *ptr, *last=NULL;
02186 
02187    ast_mutex_lock(&config_lock);
02188 
02189    for (ptr = config_engine_list; ptr; ptr=ptr->next) {
02190       if (ptr == del) {
02191          if (last)
02192             last->next = ptr->next;
02193          else
02194             config_engine_list = ptr->next;
02195          break;
02196       }
02197       last = ptr;
02198    }
02199 
02200    ast_mutex_unlock(&config_lock);
02201 
02202    return 0;
02203 }
02204 
02205 /*! \brief Find realtime engine for realtime family */
02206 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz) 
02207 {
02208    struct ast_config_engine *eng, *ret = NULL;
02209    struct ast_config_map *map;
02210 
02211    ast_mutex_lock(&config_lock);
02212 
02213    for (map = config_maps; map; map = map->next) {
02214       if (!strcasecmp(family, map->name) && (priority == map->priority)) {
02215          if (database)
02216             ast_copy_string(database, map->database, dbsiz);
02217          if (table)
02218             ast_copy_string(table, map->table ? map->table : family, tabsiz);
02219          break;
02220       }
02221    }
02222 
02223    /* Check if the required driver (engine) exist */
02224    if (map) {
02225       for (eng = config_engine_list; !ret && eng; eng = eng->next) {
02226          if (!strcasecmp(eng->name, map->driver))
02227             ret = eng;
02228       }
02229    }
02230 
02231    ast_mutex_unlock(&config_lock);
02232    
02233    /* if we found a mapping, but the engine is not available, then issue a warning */
02234    if (map && !ret)
02235       ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
02236 
02237    return ret;
02238 }
02239 
02240 static struct ast_config_engine text_file_engine = {
02241    .name = "text",
02242    .load_func = config_text_file_load,
02243 };
02244 
02245 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
02246 {
02247    char db[256];
02248    char table[256];
02249    struct ast_config_engine *loader = &text_file_engine;
02250    struct ast_config *result; 
02251 
02252    /* The config file itself bumps include_level by 1 */
02253    if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
02254       ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
02255       return NULL;
02256    }
02257 
02258    cfg->include_level++;
02259 
02260    if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
02261       struct ast_config_engine *eng;
02262 
02263       eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
02264 
02265 
02266       if (eng && eng->load_func) {
02267          loader = eng;
02268       } else {
02269          eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
02270          if (eng && eng->load_func)
02271             loader = eng;
02272       }
02273    }
02274 
02275    result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
02276 
02277    if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
02278       result->include_level--;
02279    else if (result != CONFIG_STATUS_FILEINVALID)
02280       cfg->include_level--;
02281 
02282    return result;
02283 }
02284 
02285 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
02286 {
02287    struct ast_config *cfg;
02288    struct ast_config *result;
02289 
02290    cfg = ast_config_new();
02291    if (!cfg)
02292       return NULL;
02293 
02294    result = ast_config_internal_load(filename, cfg, flags, "", who_asked);
02295    if (!result || result == CONFIG_STATUS_FILEUNCHANGED || result == CONFIG_STATUS_FILEINVALID)
02296       ast_config_destroy(cfg);
02297 
02298    return result;
02299 }
02300 
02301 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
02302 {
02303    struct ast_config_engine *eng;
02304    char db[256];
02305    char table[256];
02306    struct ast_variable *res=NULL;
02307    int i;
02308 
02309    for (i = 1; ; i++) {
02310       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02311          if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) {
02312             return res;
02313          }
02314       } else {
02315          return NULL;
02316       }
02317    }
02318 
02319    return res;
02320 }
02321 
02322 struct ast_variable *ast_load_realtime_all(const char *family, ...)
02323 {
02324    struct ast_variable *res;
02325    va_list ap;
02326 
02327    va_start(ap, family);
02328    res = ast_load_realtime_helper(family, ap);
02329    va_end(ap);
02330 
02331    return res;
02332 }
02333 
02334 struct ast_variable *ast_load_realtime(const char *family, ...)
02335 {
02336    struct ast_variable *res;
02337    struct ast_variable *cur;
02338    struct ast_variable **prev;
02339    va_list ap;
02340 
02341    va_start(ap, family);
02342    res = ast_load_realtime_helper(family, ap);
02343    va_end(ap);
02344 
02345    /* Filter the list. */
02346    prev = &res;
02347    cur = res;
02348    while (cur) {
02349       if (ast_strlen_zero(cur->value)) {
02350          /* Eliminate empty entries */
02351          struct ast_variable *next;
02352 
02353          next = cur->next;
02354          *prev = next;
02355          ast_variable_destroy(cur);
02356          cur = next;
02357       } else {
02358          /* Make blank entries empty and keep them. */
02359          if (cur->value[0] == ' ' && cur->value[1] == '\0') {
02360             char *vptr = (char *) cur->value;
02361 
02362             vptr[0] = '\0';
02363          }
02364 
02365          prev = &cur->next;
02366          cur = cur->next;
02367       }
02368    }
02369    return res;
02370 }
02371 
02372 /*! \brief Check if realtime engine is configured for family */
02373 int ast_check_realtime(const char *family)
02374 {
02375    struct ast_config_engine *eng;
02376    if (!ast_realtime_enabled()) {
02377       return 0;   /* There are no engines at all so fail early */
02378    }
02379 
02380    eng = find_engine(family, 1, NULL, 0, NULL, 0);
02381    if (eng)
02382       return 1;
02383    return 0;
02384 }
02385 
02386 /*! \brief Check if there's any realtime engines loaded */
02387 int ast_realtime_enabled(void)
02388 {
02389    return config_maps ? 1 : 0;
02390 }
02391 
02392 int ast_realtime_require_field(const char *family, ...)
02393 {
02394    struct ast_config_engine *eng;
02395    char db[256];
02396    char table[256];
02397    va_list ap;
02398    int res = -1, i;
02399 
02400    va_start(ap, family);
02401    for (i = 1; ; i++) {
02402       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02403          /* If the require succeeds, it returns 0. */
02404          if (eng->require_func && !(res = eng->require_func(db, table, ap))) {
02405             break;
02406          }
02407       } else {
02408          break;
02409       }
02410    }
02411    va_end(ap);
02412 
02413    return res;
02414 }
02415 
02416 int ast_unload_realtime(const char *family)
02417 {
02418    struct ast_config_engine *eng;
02419    char db[256];
02420    char table[256];
02421    int res = -1, i;
02422 
02423    for (i = 1; ; i++) {
02424       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02425          if (eng->unload_func) {
02426             /* Do this for ALL engines */
02427             res = eng->unload_func(db, table);
02428          }
02429       } else {
02430          break;
02431       }
02432    }
02433    return res;
02434 }
02435 
02436 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
02437 {
02438    struct ast_config_engine *eng;
02439    char db[256];
02440    char table[256];
02441    struct ast_config *res = NULL;
02442    va_list ap;
02443    int i;
02444 
02445    va_start(ap, family);
02446    for (i = 1; ; i++) {
02447       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02448          if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) {
02449             break;
02450          }
02451       } else {
02452          break;
02453       }
02454    }
02455    va_end(ap);
02456 
02457    return res;
02458 }
02459 
02460 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
02461 {
02462    struct ast_config_engine *eng;
02463    int res = -1, i;
02464    char db[256];
02465    char table[256];
02466    va_list ap;
02467 
02468    va_start(ap, lookup);
02469    for (i = 1; ; i++) {
02470       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02471          /* If the update succeeds, it returns 0. */
02472          if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) {
02473             break;
02474          }
02475       } else {
02476          break;
02477       }
02478    }
02479    va_end(ap);
02480 
02481    return res;
02482 }
02483 
02484 int ast_update2_realtime(const char *family, ...)
02485 {
02486    struct ast_config_engine *eng;
02487    int res = -1, i;
02488    char db[256];
02489    char table[256];
02490    va_list ap;
02491 
02492    va_start(ap, family);
02493    for (i = 1; ; i++) {
02494       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02495          if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) {
02496             break;
02497          }
02498       } else {
02499          break;
02500       }
02501    }
02502    va_end(ap);
02503 
02504    return res;
02505 }
02506 
02507 int ast_store_realtime(const char *family, ...)
02508 {
02509    struct ast_config_engine *eng;
02510    int res = -1, i;
02511    char db[256];
02512    char table[256];
02513    va_list ap;
02514 
02515    va_start(ap, family);
02516    for (i = 1; ; i++) {
02517       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02518          /* If the store succeeds, it returns 0. */
02519          if (eng->store_func && !(res = eng->store_func(db, table, ap))) {
02520             break;
02521          }
02522       } else {
02523          break;
02524       }
02525    }
02526    va_end(ap);
02527 
02528    return res;
02529 }
02530 
02531 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
02532 {
02533    struct ast_config_engine *eng;
02534    int res = -1, i;
02535    char db[256];
02536    char table[256];
02537    va_list ap;
02538 
02539    va_start(ap, lookup);
02540    for (i = 1; ; i++) {
02541       if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02542          if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) {
02543             break;
02544          }
02545       } else {
02546          break;
02547       }
02548    }
02549    va_end(ap);
02550 
02551    return res;
02552 }
02553 
02554 char *ast_realtime_decode_chunk(char *chunk)
02555 {
02556    char *orig = chunk;
02557    for (; *chunk; chunk++) {
02558       if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
02559          sscanf(chunk + 1, "%02hhX", chunk);
02560          memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
02561       }
02562    }
02563    return orig;
02564 }
02565 
02566 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
02567 {
02568    if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
02569       ast_str_set(dest, maxlen, "%s", chunk);
02570    } else {
02571       ast_str_reset(*dest);
02572       for (; *chunk; chunk++) {
02573          if (strchr(";^", *chunk)) {
02574             ast_str_append(dest, maxlen, "^%02hhX", *chunk);
02575          } else {
02576             ast_str_append(dest, maxlen, "%c", *chunk);
02577          }
02578       }
02579    }
02580    return ast_str_buffer(*dest);
02581 }
02582 
02583 /*! \brief Helper function to parse arguments
02584  * See documentation in config.h
02585  */
02586 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
02587    void *p_result, ...)
02588 {
02589    va_list ap;
02590    int error = 0;
02591 
02592    va_start(ap, p_result);
02593    switch (flags & PARSE_TYPE) {
02594    case PARSE_INT32:
02595        {
02596       int32_t *result = p_result;
02597       int32_t x, def = result ? *result : 0,
02598          high = (int32_t)0x7fffffff,
02599          low  = (int32_t)0x80000000;
02600       /* optional argument: first default value, then range */
02601       if (flags & PARSE_DEFAULT)
02602          def = va_arg(ap, int32_t);
02603       if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
02604          /* range requested, update bounds */
02605          low = va_arg(ap, int32_t);
02606          high = va_arg(ap, int32_t);
02607       }
02608       x = strtol(arg, NULL, 0);
02609       error = (x < low) || (x > high);
02610       if (flags & PARSE_OUT_RANGE)
02611          error = !error;
02612       if (result)
02613          *result  = error ? def : x;
02614       ast_debug(3,
02615          "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
02616          arg, low, high,
02617          result ? *result : x, error);
02618       break;
02619        }
02620 
02621    case PARSE_UINT32:
02622        {
02623       uint32_t *result = p_result;
02624       uint32_t x, def = result ? *result : 0,
02625          low = 0, high = (uint32_t)~0;
02626       /* optional argument: first default value, then range */
02627       if (flags & PARSE_DEFAULT)
02628          def = va_arg(ap, uint32_t);
02629       if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
02630          /* range requested, update bounds */
02631          low = va_arg(ap, uint32_t);
02632          high = va_arg(ap, uint32_t);
02633       }
02634       x = strtoul(arg, NULL, 0);
02635       error = (x < low) || (x > high);
02636       if (flags & PARSE_OUT_RANGE)
02637          error = !error;
02638       if (result)
02639          *result  = error ? def : x;
02640       ast_debug(3,
02641          "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
02642          arg, low, high,
02643          result ? *result : x, error);
02644       break;
02645        }
02646 
02647    case PARSE_DOUBLE:
02648        {
02649       double *result = p_result;
02650       double x, def = result ? *result : 0,
02651          low = -HUGE_VAL, high = HUGE_VAL;
02652 
02653       /* optional argument: first default value, then range */
02654       if (flags & PARSE_DEFAULT)
02655          def = va_arg(ap, double);
02656       if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
02657          /* range requested, update bounds */
02658          low = va_arg(ap, double);
02659          high = va_arg(ap, double);
02660       }
02661       x = strtod(arg, NULL);
02662       error = (x < low) || (x > high);
02663       if (flags & PARSE_OUT_RANGE)
02664          error = !error;
02665       if (result)
02666          *result  = error ? def : x;
02667       ast_debug(3,
02668          "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
02669          arg, low, high,
02670          result ? *result : x, error);
02671       break;
02672        }
02673    case PARSE_ADDR:
02674        {
02675       struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
02676 
02677       if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
02678          error = 1;
02679       }
02680 
02681       ast_debug(3, "extract addr from %s gives %s(%d)\n",
02682            arg, ast_sockaddr_stringify(addr), error);
02683 
02684       break;
02685        }
02686    case PARSE_INADDR:   /* TODO Remove this (use PARSE_ADDR instead). */
02687        {
02688       char *port, *buf;
02689       struct sockaddr_in _sa_buf;   /* buffer for the result */
02690       struct sockaddr_in *sa = p_result ?
02691          (struct sockaddr_in *)p_result : &_sa_buf;
02692       /* default is either the supplied value or the result itself */
02693       struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
02694          va_arg(ap, struct sockaddr_in *) : sa;
02695       struct hostent *hp;
02696       struct ast_hostent ahp;
02697 
02698       memset(&_sa_buf, '\0', sizeof(_sa_buf)); /* clear buffer */
02699       /* duplicate the string to strip away the :port */
02700       port = ast_strdupa(arg);
02701       buf = strsep(&port, ":");
02702       sa->sin_family = AF_INET;  /* assign family */
02703       /*
02704        * honor the ports flag setting, assign default value
02705        * in case of errors or field unset.
02706        */
02707       flags &= PARSE_PORT_MASK; /* the only flags left to process */
02708       if (port) {
02709          if (flags == PARSE_PORT_FORBID) {
02710             error = 1;  /* port was forbidden */
02711             sa->sin_port = def->sin_port;
02712          } else if (flags == PARSE_PORT_IGNORE)
02713             sa->sin_port = def->sin_port;
02714          else /* accept or require */
02715             sa->sin_port = htons(strtol(port, NULL, 0));
02716       } else {
02717          sa->sin_port = def->sin_port;
02718          if (flags == PARSE_PORT_REQUIRE)
02719             error = 1;
02720       }
02721       /* Now deal with host part, even if we have errors before. */
02722       hp = ast_gethostbyname(buf, &ahp);
02723       if (hp)  /* resolved successfully */
02724          memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
02725       else {
02726          error = 1;
02727          sa->sin_addr = def->sin_addr;
02728       }
02729       ast_debug(3,
02730          "extract inaddr from [%s] gives [%s:%d](%d)\n",
02731          arg, ast_inet_ntoa(sa->sin_addr),
02732          ntohs(sa->sin_port), error);
02733          break;
02734        }
02735    }
02736    va_end(ap);
02737    return error;
02738 }
02739 
02740 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02741 {
02742    struct ast_config_engine *eng;
02743    struct ast_config_map *map;
02744 
02745    switch (cmd) {
02746    case CLI_INIT:
02747       e->command = "core show config mappings";
02748       e->usage =
02749          "Usage: core show config mappings\n"
02750          "  Shows the filenames to config engines.\n";
02751       return NULL;
02752    case CLI_GENERATE:
02753       return NULL;
02754    }
02755    
02756    ast_mutex_lock(&config_lock);
02757 
02758    if (!config_engine_list) {
02759       ast_cli(a->fd, "No config mappings found.\n");
02760    } else {
02761       for (eng = config_engine_list; eng; eng = eng->next) {
02762          ast_cli(a->fd, "Config Engine: %s\n", eng->name);
02763          for (map = config_maps; map; map = map->next) {
02764             if (!strcasecmp(map->driver, eng->name)) {
02765                ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
02766                      map->table ? map->table : map->name);
02767             }
02768          }
02769       }
02770    }
02771    
02772    ast_mutex_unlock(&config_lock);
02773 
02774    return CLI_SUCCESS;
02775 }
02776 
02777 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02778 {
02779    struct cache_file_mtime *cfmtime;
02780    char *prev = "", *completion_value = NULL;
02781    int wordlen, which = 0;
02782 
02783    switch (cmd) {
02784    case CLI_INIT:
02785       e->command = "config reload";
02786       e->usage =
02787          "Usage: config reload <filename.conf>\n"
02788          "   Reloads all modules that reference <filename.conf>\n";
02789       return NULL;
02790    case CLI_GENERATE:
02791       if (a->pos > 2) {
02792          return NULL;
02793       }
02794 
02795       wordlen = strlen(a->word);
02796 
02797       AST_LIST_LOCK(&cfmtime_head);
02798       AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
02799          /* Skip duplicates - this only works because the list is sorted by filename */
02800          if (strcmp(cfmtime->filename, prev) == 0) {
02801             continue;
02802          }
02803 
02804          /* Core configs cannot be reloaded */
02805          if (ast_strlen_zero(cfmtime->who_asked)) {
02806             continue;
02807          }
02808 
02809          if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
02810             completion_value = ast_strdup(cfmtime->filename);
02811             break;
02812          }
02813 
02814          /* Otherwise save that we've seen this filename */
02815          prev = cfmtime->filename;
02816       }
02817       AST_LIST_UNLOCK(&cfmtime_head);
02818 
02819       return completion_value;
02820    }
02821 
02822    if (a->argc != 3) {
02823       return CLI_SHOWUSAGE;
02824    }
02825 
02826    AST_LIST_LOCK(&cfmtime_head);
02827    AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
02828       if (!strcmp(cfmtime->filename, a->argv[2])) {
02829          char *buf = alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1);
02830          sprintf(buf, "module reload %s", cfmtime->who_asked);
02831          ast_cli_command(a->fd, buf);
02832       }
02833    }
02834    AST_LIST_UNLOCK(&cfmtime_head);
02835 
02836    return CLI_SUCCESS;
02837 }
02838 
02839 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02840 {
02841    struct cache_file_mtime *cfmtime;
02842 
02843    switch (cmd) {
02844    case CLI_INIT:
02845       e->command = "config list";
02846       e->usage =
02847          "Usage: config list\n"
02848          "   Show all modules that have loaded a configuration file\n";
02849       return NULL;
02850    case CLI_GENERATE:
02851       return NULL;
02852    }
02853 
02854    AST_LIST_LOCK(&cfmtime_head);
02855    AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
02856       ast_cli(a->fd, "%-20.20s %-50s\n", S_OR(cfmtime->who_asked, "core"), cfmtime->filename);
02857    }
02858    AST_LIST_UNLOCK(&cfmtime_head);
02859 
02860    return CLI_SUCCESS;
02861 }
02862 
02863 static struct ast_cli_entry cli_config[] = {
02864    AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
02865    AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"),
02866    AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
02867 };
02868 
02869 int register_config_cli(void)
02870 {
02871    ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
02872    return 0;
02873 }