00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 #include "asterisk.h"
00034
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 423276 $")
00036
00037 #include "asterisk/paths.h"
00038 #include "asterisk/network.h"
00039 #include <time.h>
00040 #include <sys/stat.h>
00041
00042 #include <math.h>
00043
00044 #define AST_INCLUDE_GLOB 1
00045
00046 #include "asterisk/config.h"
00047 #include "asterisk/cli.h"
00048 #include "asterisk/lock.h"
00049 #include "asterisk/utils.h"
00050 #include "asterisk/channel.h"
00051 #include "asterisk/app.h"
00052 #include "asterisk/astobj2.h"
00053 #include "asterisk/strings.h"
00054 #include "asterisk/netsock2.h"
00055
00056 #define MAX_NESTED_COMMENTS 128
00057 #define COMMENT_START ";--"
00058 #define COMMENT_END "--;"
00059 #define COMMENT_META ';'
00060 #define COMMENT_TAG '-'
00061
00062
00063
00064
00065
00066
00067 #define MIN_VARIABLE_FNAME_SPACE 40
00068
00069 static char *extconfig_conf = "extconfig.conf";
00070
00071
00072
00073 struct ast_comment {
00074 struct ast_comment *next;
00075
00076 char cmt[0];
00077 };
00078
00079
00080 struct cache_file_include {
00081 AST_LIST_ENTRY(cache_file_include) list;
00082
00083 char include[0];
00084 };
00085
00086 struct cache_file_mtime {
00087 AST_LIST_ENTRY(cache_file_mtime) list;
00088 AST_LIST_HEAD_NOLOCK(includes, cache_file_include) includes;
00089 unsigned int has_exec:1;
00090
00091 unsigned long stat_size;
00092
00093 unsigned long stat_mtime_nsec;
00094
00095 time_t stat_mtime;
00096
00097
00098 const char *who_asked;
00099
00100 char filename[0];
00101 };
00102
00103
00104 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
00105
00106 static int init_appendbuf(void *data)
00107 {
00108 struct ast_str **str = data;
00109 *str = ast_str_create(16);
00110 return *str ? 0 : -1;
00111 }
00112
00113 AST_THREADSTORAGE_CUSTOM(appendbuf, init_appendbuf, ast_free_ptr);
00114
00115
00116 #define CB_SIZE 250
00117
00118 static void CB_ADD(struct ast_str **cb, const char *str)
00119 {
00120 ast_str_append(cb, 0, "%s", str);
00121 }
00122
00123 static void CB_ADD_LEN(struct ast_str **cb, const char *str, int len)
00124 {
00125 char *s = ast_alloca(len + 1);
00126
00127 memcpy(s, str, len);
00128 s[len] = '\0';
00129 ast_str_append(cb, 0, "%s", s);
00130 }
00131
00132 static void CB_RESET(struct ast_str *cb, struct ast_str *llb)
00133 {
00134 if (cb) {
00135 ast_str_reset(cb);
00136 }
00137 if (llb) {
00138 ast_str_reset(llb);
00139 }
00140 }
00141
00142 static struct ast_comment *ALLOC_COMMENT(struct ast_str *buffer)
00143 {
00144 struct ast_comment *x = NULL;
00145 if (!buffer || !ast_str_strlen(buffer)) {
00146 return NULL;
00147 }
00148 if ((x = ast_calloc(1, sizeof(*x) + ast_str_strlen(buffer) + 1))) {
00149 strcpy(x->cmt, ast_str_buffer(buffer));
00150 }
00151 return x;
00152 }
00153
00154
00155
00156
00157 struct inclfile {
00158 char *fname;
00159 int lineno;
00160 };
00161
00162 static int hash_string(const void *obj, const int flags)
00163 {
00164 char *str = ((struct inclfile *) obj)->fname;
00165 int total;
00166
00167 for (total = 0; *str; str++) {
00168 unsigned int tmp = total;
00169 total <<= 1;
00170 total += tmp;
00171 total <<= 2;
00172 total += tmp;
00173
00174 total += ((unsigned int) (*str));
00175 }
00176 if (total < 0) {
00177 total = -total;
00178 }
00179 return total;
00180 }
00181
00182 static int hashtab_compare_strings(void *a, void *b, int flags)
00183 {
00184 const struct inclfile *ae = a, *be = b;
00185 return !strcmp(ae->fname, be->fname) ? CMP_MATCH | CMP_STOP : 0;
00186 }
00187
00188 static struct ast_config_map {
00189 struct ast_config_map *next;
00190 int priority;
00191
00192 const char *name;
00193
00194 const char *driver;
00195
00196 const char *database;
00197
00198 const char *table;
00199
00200 char stuff[0];
00201 } *config_maps = NULL;
00202
00203 AST_MUTEX_DEFINE_STATIC(config_lock);
00204 static struct ast_config_engine *config_engine_list;
00205
00206 #define MAX_INCLUDE_LEVEL 10
00207
00208 struct ast_category_template_instance {
00209 char name[80];
00210 const struct ast_category *inst;
00211 AST_LIST_ENTRY(ast_category_template_instance) next;
00212 };
00213
00214 struct ast_category {
00215 char name[80];
00216 int ignored;
00217 int include_level;
00218
00219
00220
00221
00222 char *file;
00223 int lineno;
00224 AST_LIST_HEAD_NOLOCK(template_instance_list, ast_category_template_instance) template_instances;
00225 struct ast_comment *precomments;
00226 struct ast_comment *sameline;
00227 struct ast_comment *trailing;
00228
00229 struct ast_variable *root;
00230
00231 struct ast_variable *last;
00232
00233 struct ast_category *next;
00234 };
00235
00236 struct ast_config {
00237
00238 struct ast_category *root;
00239
00240 struct ast_category *last;
00241 struct ast_category *current;
00242 struct ast_category *last_browse;
00243 int include_level;
00244 int max_include_level;
00245 struct ast_config_include *includes;
00246 };
00247
00248 struct ast_config_include {
00249
00250
00251
00252
00253 char *include_location_file;
00254 int include_location_lineno;
00255 int exec;
00256
00257
00258
00259
00260 char *exec_file;
00261
00262
00263
00264
00265 char *included_file;
00266 int inclusion_count;
00267
00268 int output;
00269 struct ast_config_include *next;
00270 };
00271
00272 static void ast_variable_destroy(struct ast_variable *doomed);
00273 static void ast_includes_destroy(struct ast_config_include *incls);
00274
00275 #ifdef MALLOC_DEBUG
00276 struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno)
00277 #else
00278 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename)
00279 #endif
00280 {
00281 struct ast_variable *variable;
00282 int name_len = strlen(name) + 1;
00283 int val_len = strlen(value) + 1;
00284 int fn_len = strlen(filename) + 1;
00285
00286
00287 if (fn_len < MIN_VARIABLE_FNAME_SPACE) {
00288 fn_len = MIN_VARIABLE_FNAME_SPACE;
00289 }
00290
00291 if (
00292 #ifdef MALLOC_DEBUG
00293 (variable = __ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable), file, lineno, func))
00294 #else
00295 (variable = ast_calloc(1, fn_len + name_len + val_len + sizeof(*variable)))
00296 #endif
00297 ) {
00298 char *dst = variable->stuff;
00299
00300
00301 variable->file = strcpy(dst, filename);
00302 dst += fn_len;
00303 variable->name = strcpy(dst, name);
00304 dst += name_len;
00305 variable->value = strcpy(dst, value);
00306 }
00307 return variable;
00308 }
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319 static void ast_variable_move(struct ast_variable *dst_var, struct ast_variable *src_var)
00320 {
00321 dst_var->lineno = src_var->lineno;
00322 dst_var->object = src_var->object;
00323 dst_var->blanklines = src_var->blanklines;
00324 dst_var->precomments = src_var->precomments;
00325 src_var->precomments = NULL;
00326 dst_var->sameline = src_var->sameline;
00327 src_var->sameline = NULL;
00328 dst_var->trailing = src_var->trailing;
00329 src_var->trailing = NULL;
00330 }
00331
00332 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)
00333 {
00334
00335
00336
00337
00338 struct ast_config_include *inc;
00339 struct stat statbuf;
00340
00341 inc = ast_include_find(conf, included_file);
00342 if (inc) {
00343 do {
00344 inc->inclusion_count++;
00345 snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
00346 } while (stat(real_included_file_name, &statbuf) == 0);
00347 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);
00348 } else
00349 *real_included_file_name = 0;
00350
00351 inc = ast_calloc(1,sizeof(struct ast_config_include));
00352 if (!inc) {
00353 return NULL;
00354 }
00355 inc->include_location_file = ast_strdup(from_file);
00356 inc->include_location_lineno = from_lineno;
00357 if (!ast_strlen_zero(real_included_file_name))
00358 inc->included_file = ast_strdup(real_included_file_name);
00359 else
00360 inc->included_file = ast_strdup(included_file);
00361
00362 inc->exec = is_exec;
00363 if (is_exec)
00364 inc->exec_file = ast_strdup(exec_file);
00365
00366 if (!inc->include_location_file
00367 || !inc->included_file
00368 || (is_exec && !inc->exec_file)) {
00369 ast_includes_destroy(inc);
00370 return NULL;
00371 }
00372
00373
00374 inc->next = conf->includes;
00375 conf->includes = inc;
00376
00377 return inc;
00378 }
00379
00380 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
00381 {
00382 struct ast_config_include *incl;
00383 struct ast_category *cat;
00384 char *str;
00385
00386 int from_len = strlen(from_file);
00387 int to_len = strlen(to_file);
00388
00389 if (strcmp(from_file, to_file) == 0)
00390 return;
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401 for (incl = conf->includes; incl; incl=incl->next) {
00402 if (strcmp(incl->include_location_file,from_file) == 0) {
00403 if (from_len >= to_len)
00404 strcpy(incl->include_location_file, to_file);
00405 else {
00406
00407 str = ast_strdup(to_file);
00408 if (str) {
00409 ast_free(incl->include_location_file);
00410 incl->include_location_file = str;
00411 }
00412 }
00413 }
00414 }
00415 for (cat = conf->root; cat; cat = cat->next) {
00416 struct ast_variable **prev;
00417 struct ast_variable *v;
00418 struct ast_variable *new_var;
00419
00420 if (strcmp(cat->file,from_file) == 0) {
00421 if (from_len >= to_len)
00422 strcpy(cat->file, to_file);
00423 else {
00424
00425 str = ast_strdup(to_file);
00426 if (str) {
00427 ast_free(cat->file);
00428 cat->file = str;
00429 }
00430 }
00431 }
00432 for (prev = &cat->root, v = cat->root; v; prev = &v->next, v = v->next) {
00433 if (strcmp(v->file, from_file)) {
00434 continue;
00435 }
00436
00437
00438
00439
00440
00441
00442 if (to_len < v->name - v->file) {
00443
00444 str = (char *) v->file;
00445 strcpy(str, to_file);
00446 continue;
00447 }
00448
00449
00450 new_var = ast_variable_new(v->name, v->value, to_file);
00451 if (!new_var) {
00452 continue;
00453 }
00454
00455
00456 ast_variable_move(new_var, v);
00457
00458
00459 new_var->next = v->next;
00460 if (cat->last == v) {
00461 cat->last = new_var;
00462 }
00463 *prev = new_var;
00464
00465 ast_variable_destroy(v);
00466
00467 v = new_var;
00468 }
00469 }
00470 }
00471
00472 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
00473 {
00474 struct ast_config_include *x;
00475 for (x=conf->includes;x;x=x->next) {
00476 if (strcmp(x->included_file,included_file) == 0)
00477 return x;
00478 }
00479 return 0;
00480 }
00481
00482
00483 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
00484 {
00485 if (!variable)
00486 return;
00487 if (category->last)
00488 category->last->next = variable;
00489 else
00490 category->root = variable;
00491 category->last = variable;
00492 while (category->last->next)
00493 category->last = category->last->next;
00494 }
00495
00496 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
00497 {
00498 struct ast_variable *cur = category->root;
00499 int lineno;
00500 int insertline;
00501
00502 if (!variable || sscanf(line, "%30d", &insertline) != 1) {
00503 return;
00504 }
00505 if (!insertline) {
00506 variable->next = category->root;
00507 category->root = variable;
00508 } else {
00509 for (lineno = 1; lineno < insertline; lineno++) {
00510 cur = cur->next;
00511 if (!cur->next) {
00512 break;
00513 }
00514 }
00515 variable->next = cur->next;
00516 cur->next = variable;
00517 }
00518 }
00519
00520 static void ast_comment_destroy(struct ast_comment **comment)
00521 {
00522 struct ast_comment *n, *p;
00523
00524 for (p = *comment; p; p = n) {
00525 n = p->next;
00526 ast_free(p);
00527 }
00528
00529 *comment = NULL;
00530 }
00531
00532 static void ast_variable_destroy(struct ast_variable *doomed)
00533 {
00534 ast_comment_destroy(&doomed->precomments);
00535 ast_comment_destroy(&doomed->sameline);
00536 ast_comment_destroy(&doomed->trailing);
00537 ast_free(doomed);
00538 }
00539
00540 struct ast_variable *ast_variables_dup(struct ast_variable *var)
00541 {
00542 struct ast_variable *cloned;
00543 struct ast_variable *tmp;
00544
00545 if (!(cloned = ast_variable_new(var->name, var->value, var->file))) {
00546 return NULL;
00547 }
00548
00549 tmp = cloned;
00550
00551 while ((var = var->next)) {
00552 if (!(tmp->next = ast_variable_new(var->name, var->value, var->file))) {
00553 ast_variables_destroy(cloned);
00554 return NULL;
00555 }
00556 tmp = tmp->next;
00557 }
00558
00559 return cloned;
00560 }
00561
00562 struct ast_variable *ast_variables_reverse(struct ast_variable *var)
00563 {
00564 struct ast_variable *var1, *var2;
00565
00566 var1 = var;
00567
00568 if (!var1 || !var1->next) {
00569 return var1;
00570 }
00571
00572 var2 = var1->next;
00573 var1->next = NULL;
00574
00575 while (var2) {
00576 struct ast_variable *next = var2->next;
00577
00578 var2->next = var1;
00579 var1 = var2;
00580 var2 = next;
00581 }
00582
00583 return var1;
00584 }
00585
00586 void ast_variables_destroy(struct ast_variable *v)
00587 {
00588 struct ast_variable *vn;
00589
00590 while (v) {
00591 vn = v;
00592 v = v->next;
00593 ast_variable_destroy(vn);
00594 }
00595 }
00596
00597 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
00598 {
00599 struct ast_category *cat = NULL;
00600
00601 if (!category) {
00602 return NULL;
00603 }
00604
00605 if (config->last_browse && (config->last_browse->name == category)) {
00606 cat = config->last_browse;
00607 } else {
00608 cat = ast_category_get(config, category);
00609 }
00610
00611 return (cat) ? cat->root : NULL;
00612 }
00613
00614 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
00615 {
00616 const char *tmp;
00617 tmp = ast_variable_retrieve(cfg, cat, var);
00618 if (!tmp) {
00619 tmp = ast_variable_retrieve(cfg, "general", var);
00620 }
00621 return tmp;
00622 }
00623
00624
00625 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
00626 {
00627 struct ast_variable *v;
00628
00629 if (category) {
00630 for (v = ast_variable_browse(config, category); v; v = v->next) {
00631 if (!strcasecmp(variable, v->name)) {
00632 return v->value;
00633 }
00634 }
00635 } else {
00636 struct ast_category *cat;
00637
00638 for (cat = config->root; cat; cat = cat->next) {
00639 for (v = cat->root; v; v = v->next) {
00640 if (!strcasecmp(variable, v->name)) {
00641 return v->value;
00642 }
00643 }
00644 }
00645 }
00646
00647 return NULL;
00648 }
00649
00650 static struct ast_variable *variable_clone(const struct ast_variable *old)
00651 {
00652 struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
00653
00654 if (new) {
00655 new->lineno = old->lineno;
00656 new->object = old->object;
00657 new->blanklines = old->blanklines;
00658
00659 }
00660
00661 return new;
00662 }
00663
00664 static void move_variables(struct ast_category *old, struct ast_category *new)
00665 {
00666 struct ast_variable *var = old->root;
00667
00668 old->root = NULL;
00669
00670 ast_variable_append(new, var);
00671 }
00672
00673 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
00674 {
00675 struct ast_category *category;
00676
00677 category = ast_calloc(1, sizeof(*category));
00678 if (!category) {
00679 return NULL;
00680 }
00681 category->file = ast_strdup(in_file);
00682 if (!category->file) {
00683 ast_category_destroy(category);
00684 return NULL;
00685 }
00686 ast_copy_string(category->name, name, sizeof(category->name));
00687 category->lineno = lineno;
00688 return category;
00689 }
00690
00691 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
00692 {
00693 struct ast_category *cat;
00694
00695
00696 for (cat = config->root; cat; cat = cat->next) {
00697 if (cat->name == category_name && (ignored || !cat->ignored))
00698 return cat;
00699 }
00700
00701 for (cat = config->root; cat; cat = cat->next) {
00702 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
00703 return cat;
00704 }
00705
00706 return NULL;
00707 }
00708
00709 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
00710 {
00711 return category_get(config, category_name, 0);
00712 }
00713
00714 int ast_category_exist(const struct ast_config *config, const char *category_name)
00715 {
00716 return !!ast_category_get(config, category_name);
00717 }
00718
00719 void ast_category_append(struct ast_config *config, struct ast_category *category)
00720 {
00721 if (config->last)
00722 config->last->next = category;
00723 else
00724 config->root = category;
00725 category->include_level = config->include_level;
00726 config->last = category;
00727 config->current = category;
00728 }
00729
00730 int ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
00731 {
00732 struct ast_category *cur_category;
00733
00734 if (!config || !cat || !match) {
00735 return -1;
00736 }
00737 if (!strcasecmp(config->root->name, match)) {
00738 cat->next = config->root;
00739 config->root = cat;
00740 return 0;
00741 }
00742 for (cur_category = config->root; cur_category && cur_category->next;
00743 cur_category = cur_category->next) {
00744 if (!strcasecmp(cur_category->next->name, match)) {
00745 cat->next = cur_category->next;
00746 cur_category->next = cat;
00747 return 0;
00748 }
00749 }
00750
00751 return -1;
00752 }
00753
00754 static void ast_destroy_template_list(struct ast_category *cat)
00755 {
00756 struct ast_category_template_instance *x;
00757
00758 while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
00759 ast_free(x);
00760 }
00761
00762 void ast_category_destroy(struct ast_category *cat)
00763 {
00764 ast_variables_destroy(cat->root);
00765 cat->root = NULL;
00766 cat->last = NULL;
00767 ast_comment_destroy(&cat->precomments);
00768 ast_comment_destroy(&cat->sameline);
00769 ast_comment_destroy(&cat->trailing);
00770 ast_destroy_template_list(cat);
00771 ast_free(cat->file);
00772 ast_free(cat);
00773 }
00774
00775 static void ast_includes_destroy(struct ast_config_include *incls)
00776 {
00777 struct ast_config_include *incl,*inclnext;
00778
00779 for (incl=incls; incl; incl = inclnext) {
00780 inclnext = incl->next;
00781 ast_free(incl->include_location_file);
00782 ast_free(incl->exec_file);
00783 ast_free(incl->included_file);
00784 ast_free(incl);
00785 }
00786 }
00787
00788 static struct ast_category *next_available_category(struct ast_category *cat)
00789 {
00790 for (; cat && cat->ignored; cat = cat->next);
00791
00792 return cat;
00793 }
00794
00795
00796 struct ast_variable *ast_category_first(struct ast_category *cat)
00797 {
00798 return (cat) ? cat->root : NULL;
00799 }
00800
00801 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
00802 {
00803 struct ast_category *category = ast_category_get(config, cat);
00804
00805 if (category)
00806 return category->root;
00807 return NULL;
00808 }
00809
00810 char *ast_category_browse(struct ast_config *config, const char *prev)
00811 {
00812 struct ast_category *cat;
00813
00814 if (!prev) {
00815
00816 cat = config->root;
00817 } else if (config->last_browse && (config->last_browse->name == prev)) {
00818
00819 cat = config->last_browse->next;
00820 } else {
00821
00822
00823
00824
00825
00826
00827 for (cat = config->root; cat; cat = cat->next) {
00828 if (cat->name == prev) {
00829
00830 cat = cat->next;
00831 break;
00832 }
00833 }
00834 if (!cat) {
00835
00836
00837
00838
00839 for (cat = config->root; cat; cat = cat->next) {
00840 if (!strcasecmp(cat->name, prev)) {
00841
00842 cat = cat->next;
00843 break;
00844 }
00845 }
00846 }
00847 }
00848
00849 if (cat)
00850 cat = next_available_category(cat);
00851
00852 config->last_browse = cat;
00853 return (cat) ? cat->name : NULL;
00854 }
00855
00856 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
00857 {
00858 struct ast_variable *v;
00859
00860 v = cat->root;
00861 cat->root = NULL;
00862 cat->last = NULL;
00863
00864 return v;
00865 }
00866
00867 void ast_category_rename(struct ast_category *cat, const char *name)
00868 {
00869 ast_copy_string(cat->name, name, sizeof(cat->name));
00870 }
00871
00872 static void inherit_category(struct ast_category *new, const struct ast_category *base)
00873 {
00874 struct ast_variable *var;
00875 struct ast_category_template_instance *x;
00876
00877 x = ast_calloc(1, sizeof(*x));
00878 if (!x) {
00879 return;
00880 }
00881 strcpy(x->name, base->name);
00882 x->inst = base;
00883 AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
00884 for (var = base->root; var; var = var->next)
00885 ast_variable_append(new, variable_clone(var));
00886 }
00887
00888 struct ast_config *ast_config_new(void)
00889 {
00890 struct ast_config *config;
00891
00892 if ((config = ast_calloc(1, sizeof(*config))))
00893 config->max_include_level = MAX_INCLUDE_LEVEL;
00894 return config;
00895 }
00896
00897 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
00898 {
00899 struct ast_variable *cur, *prev=NULL, *curn;
00900 int res = -1;
00901 int num_item = 0;
00902 int req_item;
00903
00904 req_item = -1;
00905 if (!ast_strlen_zero(line)) {
00906
00907 if (sscanf(line, "%30d", &req_item) != 1
00908 || req_item < 0) {
00909
00910 return -1;
00911 }
00912 }
00913
00914 prev = NULL;
00915 cur = category->root;
00916 while (cur) {
00917 curn = cur->next;
00918
00919 if ((0 <= req_item && num_item == req_item)
00920 || (req_item < 0 && !strcasecmp(cur->name, variable)
00921 && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
00922 if (prev) {
00923 prev->next = cur->next;
00924 if (cur == category->last)
00925 category->last = prev;
00926 } else {
00927 category->root = cur->next;
00928 if (cur == category->last)
00929 category->last = NULL;
00930 }
00931 ast_variable_destroy(cur);
00932 res = 0;
00933 } else
00934 prev = cur;
00935
00936 cur = curn;
00937 ++num_item;
00938 }
00939 return res;
00940 }
00941
00942 int ast_variable_update(struct ast_category *category, const char *variable,
00943 const char *value, const char *match, unsigned int object)
00944 {
00945 struct ast_variable *cur, *prev=NULL, *newer=NULL;
00946
00947 for (cur = category->root; cur; prev = cur, cur = cur->next) {
00948 if (strcasecmp(cur->name, variable) ||
00949 (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
00950 continue;
00951
00952 if (!(newer = ast_variable_new(variable, value, cur->file)))
00953 return -1;
00954
00955 ast_variable_move(newer, cur);
00956 newer->object = newer->object || object;
00957
00958
00959 newer->next = cur->next;
00960 if (prev)
00961 prev->next = newer;
00962 else
00963 category->root = newer;
00964 if (category->last == cur)
00965 category->last = newer;
00966
00967 ast_variable_destroy(cur);
00968
00969 return 0;
00970 }
00971
00972
00973 return -1;
00974 }
00975
00976 int ast_category_delete(struct ast_config *cfg, const char *category)
00977 {
00978 struct ast_category *prev=NULL, *cat;
00979
00980 cat = cfg->root;
00981 while (cat) {
00982 if (cat->name == category) {
00983 if (prev) {
00984 prev->next = cat->next;
00985 if (cat == cfg->last)
00986 cfg->last = prev;
00987 } else {
00988 cfg->root = cat->next;
00989 if (cat == cfg->last)
00990 cfg->last = NULL;
00991 }
00992 ast_category_destroy(cat);
00993 return 0;
00994 }
00995 prev = cat;
00996 cat = cat->next;
00997 }
00998
00999 prev = NULL;
01000 cat = cfg->root;
01001 while (cat) {
01002 if (!strcasecmp(cat->name, category)) {
01003 if (prev) {
01004 prev->next = cat->next;
01005 if (cat == cfg->last)
01006 cfg->last = prev;
01007 } else {
01008 cfg->root = cat->next;
01009 if (cat == cfg->last)
01010 cfg->last = NULL;
01011 }
01012 ast_category_destroy(cat);
01013 return 0;
01014 }
01015 prev = cat;
01016 cat = cat->next;
01017 }
01018 return -1;
01019 }
01020
01021 int ast_category_empty(struct ast_config *cfg, const char *category)
01022 {
01023 struct ast_category *cat;
01024
01025 for (cat = cfg->root; cat; cat = cat->next) {
01026 if (strcasecmp(cat->name, category))
01027 continue;
01028 ast_variables_destroy(cat->root);
01029 cat->root = NULL;
01030 cat->last = NULL;
01031 return 0;
01032 }
01033
01034 return -1;
01035 }
01036
01037 void ast_config_destroy(struct ast_config *cfg)
01038 {
01039 struct ast_category *cat, *catn;
01040
01041 if (!cfg)
01042 return;
01043
01044 ast_includes_destroy(cfg->includes);
01045
01046 cat = cfg->root;
01047 while (cat) {
01048 catn = cat;
01049 cat = cat->next;
01050 ast_category_destroy(catn);
01051 }
01052 ast_free(cfg);
01053 }
01054
01055 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
01056 {
01057 return cfg->current;
01058 }
01059
01060 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
01061 {
01062
01063 cfg->current = (struct ast_category *) cat;
01064 }
01065
01066
01067
01068
01069
01070
01071
01072
01073
01074
01075
01076 static struct cache_file_mtime *cfmtime_new(const char *filename, const char *who_asked)
01077 {
01078 struct cache_file_mtime *cfmtime;
01079 char *dst;
01080
01081 cfmtime = ast_calloc(1,
01082 sizeof(*cfmtime) + strlen(filename) + 1 + strlen(who_asked) + 1);
01083 if (!cfmtime) {
01084 return NULL;
01085 }
01086 dst = cfmtime->filename;
01087 strcpy(dst, filename);
01088 dst += strlen(dst) + 1;
01089 cfmtime->who_asked = strcpy(dst, who_asked);
01090
01091 return cfmtime;
01092 }
01093
01094 enum config_cache_attribute_enum {
01095 ATTRIBUTE_INCLUDE = 0,
01096 ATTRIBUTE_EXEC = 1,
01097 };
01098
01099
01100
01101
01102
01103
01104
01105
01106
01107
01108 static void cfmstat_save(struct cache_file_mtime *cfmtime, struct stat *statbuf)
01109 {
01110 cfmtime->stat_size = statbuf->st_size;
01111 #if defined(HAVE_STRUCT_STAT_ST_MTIM)
01112 cfmtime->stat_mtime_nsec = statbuf->st_mtim.tv_nsec;
01113 #elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC)
01114 cfmtime->stat_mtime_nsec = statbuf->st_mtimensec;
01115 #elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
01116 cfmtime->stat_mtime_nsec = statbuf->st_mtimespec.tv_nsec;
01117 #else
01118 cfmtime->stat_mtime_nsec = 0;
01119 #endif
01120 cfmtime->stat_mtime = statbuf->st_mtime;
01121 }
01122
01123
01124
01125
01126
01127
01128
01129
01130
01131
01132 static int cfmstat_cmp(struct cache_file_mtime *cfmtime, struct stat *statbuf)
01133 {
01134 struct cache_file_mtime cfm_buf;
01135
01136 cfmstat_save(&cfm_buf, statbuf);
01137
01138 return cfmtime->stat_size != cfm_buf.stat_size
01139 || cfmtime->stat_mtime != cfm_buf.stat_mtime
01140 || cfmtime->stat_mtime_nsec != cfm_buf.stat_mtime_nsec;
01141 }
01142
01143
01144
01145
01146
01147
01148
01149
01150
01151
01152
01153 static void config_cache_flush_includes(struct cache_file_mtime *cfmtime)
01154 {
01155 struct cache_file_include *cfinclude;
01156
01157 while ((cfinclude = AST_LIST_REMOVE_HEAD(&cfmtime->includes, list))) {
01158 ast_free(cfinclude);
01159 }
01160 }
01161
01162
01163
01164
01165
01166
01167
01168
01169
01170
01171
01172 static void config_cache_destroy_entry(struct cache_file_mtime *cfmtime)
01173 {
01174 config_cache_flush_includes(cfmtime);
01175 ast_free(cfmtime);
01176 }
01177
01178
01179
01180
01181
01182
01183
01184
01185
01186
01187 static void config_cache_remove(const char *filename, const char *who_asked)
01188 {
01189 struct cache_file_mtime *cfmtime;
01190
01191 AST_LIST_LOCK(&cfmtime_head);
01192 AST_LIST_TRAVERSE_SAFE_BEGIN(&cfmtime_head, cfmtime, list) {
01193 if (!strcmp(cfmtime->filename, filename)
01194 && !strcmp(cfmtime->who_asked, who_asked)) {
01195 AST_LIST_REMOVE_CURRENT(list);
01196 config_cache_destroy_entry(cfmtime);
01197 break;
01198 }
01199 }
01200 AST_LIST_TRAVERSE_SAFE_END;
01201 AST_LIST_UNLOCK(&cfmtime_head);
01202 }
01203
01204 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
01205 {
01206 struct cache_file_mtime *cfmtime;
01207 struct cache_file_include *cfinclude;
01208
01209
01210 AST_LIST_LOCK(&cfmtime_head);
01211 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
01212 if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
01213 break;
01214 }
01215 if (!cfmtime) {
01216 cfmtime = cfmtime_new(configfile, who_asked);
01217 if (!cfmtime) {
01218 AST_LIST_UNLOCK(&cfmtime_head);
01219 return;
01220 }
01221
01222 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
01223 }
01224
01225 switch (attrtype) {
01226 case ATTRIBUTE_INCLUDE:
01227 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
01228 if (!strcmp(cfinclude->include, filename)) {
01229 AST_LIST_UNLOCK(&cfmtime_head);
01230 return;
01231 }
01232 }
01233 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
01234 if (!cfinclude) {
01235 AST_LIST_UNLOCK(&cfmtime_head);
01236 return;
01237 }
01238 strcpy(cfinclude->include, filename);
01239 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
01240 break;
01241 case ATTRIBUTE_EXEC:
01242 cfmtime->has_exec = 1;
01243 break;
01244 }
01245 AST_LIST_UNLOCK(&cfmtime_head);
01246 }
01247
01248
01249
01250
01251
01252
01253
01254
01255 static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
01256 char *buf, int lineno, const char *configfile, struct ast_flags flags,
01257 struct ast_str *comment_buffer,
01258 struct ast_str *lline_buffer,
01259 const char *suggested_include_file,
01260 struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
01261 {
01262 char *c;
01263 char *cur = buf;
01264 struct ast_variable *v;
01265 char cmd[512], exec_file[512];
01266
01267
01268 if (cur[0] == '[') {
01269
01270
01271
01272
01273
01274
01275
01276
01277 struct ast_category *newcat = NULL;
01278 char *catname;
01279
01280 c = strchr(cur, ']');
01281 if (!c) {
01282 ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
01283 return -1;
01284 }
01285 *c++ = '\0';
01286 cur++;
01287 if (*c++ != '(')
01288 c = NULL;
01289 catname = cur;
01290 if (!(*cat = newcat = ast_category_new(catname,
01291 S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
01292 lineno))) {
01293 return -1;
01294 }
01295 (*cat)->lineno = lineno;
01296 *last_var = 0;
01297 *last_cat = newcat;
01298
01299
01300 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01301 newcat->precomments = ALLOC_COMMENT(comment_buffer);
01302 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01303 newcat->sameline = ALLOC_COMMENT(lline_buffer);
01304 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01305 CB_RESET(comment_buffer, lline_buffer);
01306
01307
01308 if (c) {
01309 if (!(cur = strchr(c, ')'))) {
01310 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
01311 return -1;
01312 }
01313 *cur = '\0';
01314 while ((cur = strsep(&c, ","))) {
01315 if (!strcasecmp(cur, "!")) {
01316 (*cat)->ignored = 1;
01317 } else if (!strcasecmp(cur, "+")) {
01318 *cat = category_get(cfg, catname, 1);
01319 if (!(*cat)) {
01320 if (newcat)
01321 ast_category_destroy(newcat);
01322 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
01323 return -1;
01324 }
01325 if (newcat) {
01326 move_variables(newcat, *cat);
01327 ast_category_destroy(newcat);
01328 newcat = NULL;
01329 }
01330 } else {
01331 struct ast_category *base;
01332
01333 base = category_get(cfg, cur, 1);
01334 if (!base) {
01335 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
01336 return -1;
01337 }
01338 inherit_category(*cat, base);
01339 }
01340 }
01341 }
01342 if (newcat)
01343 ast_category_append(cfg, *cat);
01344 } else if (cur[0] == '#') {
01345 char *cur2;
01346 char real_inclusion_name[256];
01347 int do_include = 0;
01348
01349 cur++;
01350 c = cur;
01351 while (*c && (*c > 32)) {
01352 c++;
01353 }
01354
01355 if (*c) {
01356 *c = '\0';
01357
01358 c = ast_strip(c + 1);
01359 if (!(*c)) {
01360 c = NULL;
01361 }
01362 } else {
01363 c = NULL;
01364 }
01365 if (!strcasecmp(cur, "include")) {
01366 do_include = 1;
01367 } else if (!strcasecmp(cur, "exec")) {
01368 if (!ast_opt_exec_includes) {
01369 ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
01370 return 0;
01371 }
01372 } else {
01373 ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
01374 return 0;
01375 }
01376
01377 if (c == NULL) {
01378 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
01379 do_include ? "include" : "exec",
01380 do_include ? "filename" : "/path/to/executable",
01381 lineno,
01382 configfile);
01383 return 0;
01384 }
01385
01386 cur = c;
01387
01388
01389 if ((*c == '"') || (*c == '<')) {
01390 char quote_char = *c;
01391 if (quote_char == '<') {
01392 quote_char = '>';
01393 }
01394
01395 if (*(c + strlen(c) - 1) == quote_char) {
01396 cur++;
01397 *(c + strlen(c) - 1) = '\0';
01398 }
01399 }
01400 cur2 = cur;
01401
01402
01403
01404 if (!do_include) {
01405 struct timeval now = ast_tvnow();
01406 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
01407 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
01408 snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
01409 snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
01410 ast_safe_system(cmd);
01411 cur = exec_file;
01412 } else {
01413 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
01414 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
01415 exec_file[0] = '\0';
01416 }
01417
01418
01419 ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
01420
01421 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
01422 if (!ast_strlen_zero(exec_file))
01423 unlink(exec_file);
01424 if (!do_include) {
01425 ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
01426 return -1;
01427 }
01428
01429
01430 } else {
01431
01432 int object = 0;
01433 if (!(*cat)) {
01434 ast_log(LOG_WARNING,
01435 "parse error: No category context for line %d of %s\n", lineno, configfile);
01436 return -1;
01437 }
01438 c = strchr(cur, '=');
01439
01440 if (c && c > cur && (*(c - 1) == '+')) {
01441 struct ast_variable *var, *replace = NULL;
01442 struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
01443
01444 if (!str || !*str) {
01445 return -1;
01446 }
01447
01448 *(c - 1) = '\0';
01449 c++;
01450 cur = ast_strip(cur);
01451
01452
01453 for (var = ast_category_first(*cat); var; var = var->next) {
01454 if (!strcmp(var->name, cur)) {
01455 replace = var;
01456 }
01457 }
01458
01459 if (!replace) {
01460
01461 goto set_new_variable;
01462 }
01463
01464 ast_str_set(str, 0, "%s", replace->value);
01465 ast_str_append(str, 0, "%s", c);
01466 ast_str_trim_blanks(*str);
01467 ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
01468 } else if (c) {
01469 *c = 0;
01470 c++;
01471
01472 if (*c== '>') {
01473 object = 1;
01474 c++;
01475 }
01476 set_new_variable:
01477 if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
01478 v->lineno = lineno;
01479 v->object = object;
01480 *last_cat = 0;
01481 *last_var = v;
01482
01483 v->blanklines = 0;
01484 ast_variable_append(*cat, v);
01485
01486 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01487 v->precomments = ALLOC_COMMENT(comment_buffer);
01488 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01489 v->sameline = ALLOC_COMMENT(lline_buffer);
01490 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01491 CB_RESET(comment_buffer, lline_buffer);
01492
01493 } else {
01494 return -1;
01495 }
01496 } else {
01497 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
01498 }
01499 }
01500 return 0;
01501 }
01502
01503 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)
01504 {
01505 char fn[256];
01506 #if defined(LOW_MEMORY)
01507 char buf[512];
01508 #else
01509 char buf[8192];
01510 #endif
01511 char *new_buf, *comment_p, *process_buf;
01512 FILE *f;
01513 int lineno=0;
01514 int comment = 0, nest[MAX_NESTED_COMMENTS];
01515 struct ast_category *cat = NULL;
01516 int count = 0;
01517 struct stat statbuf;
01518 struct cache_file_mtime *cfmtime = NULL;
01519 struct cache_file_include *cfinclude;
01520 struct ast_variable *last_var = 0;
01521 struct ast_category *last_cat = 0;
01522
01523 struct ast_str *comment_buffer = NULL;
01524 struct ast_str *lline_buffer = NULL;
01525
01526 if (cfg)
01527 cat = ast_config_get_current_category(cfg);
01528
01529 if (filename[0] == '/') {
01530 ast_copy_string(fn, filename, sizeof(fn));
01531 } else {
01532 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
01533 }
01534
01535 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01536 comment_buffer = ast_str_create(CB_SIZE);
01537 if (comment_buffer)
01538 lline_buffer = ast_str_create(CB_SIZE);
01539 if (!lline_buffer) {
01540 ast_free(comment_buffer);
01541 ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
01542 return NULL;
01543 }
01544 }
01545 #ifdef AST_INCLUDE_GLOB
01546 {
01547 int glob_ret;
01548 glob_t globbuf;
01549
01550 globbuf.gl_offs = 0;
01551 glob_ret = glob(fn, MY_GLOB_FLAGS, NULL, &globbuf);
01552 if (glob_ret == GLOB_NOSPACE) {
01553 ast_log(LOG_WARNING,
01554 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
01555 } else if (glob_ret == GLOB_ABORTED) {
01556 ast_log(LOG_WARNING,
01557 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
01558 } else {
01559
01560 int i;
01561
01562 if (!cfg && (globbuf.gl_pathc != 1 || strcmp(fn, globbuf.gl_pathv[0]))) {
01563
01564
01565
01566
01567
01568
01569
01570 globfree(&globbuf);
01571 ast_free(comment_buffer);
01572 ast_free(lline_buffer);
01573 return NULL;
01574 }
01575 for (i=0; i<globbuf.gl_pathc; i++) {
01576 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
01577 #endif
01578
01579
01580
01581
01582
01583 do {
01584 if (stat(fn, &statbuf)) {
01585 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
01586 config_cache_remove(fn, who_asked);
01587 }
01588 continue;
01589 }
01590
01591 if (!S_ISREG(statbuf.st_mode)) {
01592 ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
01593 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
01594 config_cache_remove(fn, who_asked);
01595 }
01596 continue;
01597 }
01598
01599 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
01600
01601 AST_LIST_LOCK(&cfmtime_head);
01602 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
01603 if (!strcmp(cfmtime->filename, fn) && !strcmp(cfmtime->who_asked, who_asked))
01604 break;
01605 }
01606 if (!cfmtime) {
01607 cfmtime = cfmtime_new(fn, who_asked);
01608 if (!cfmtime) {
01609 AST_LIST_UNLOCK(&cfmtime_head);
01610 continue;
01611 }
01612
01613 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
01614 }
01615 }
01616
01617 if (cfmtime
01618 && !cfmtime->has_exec
01619 && !cfmstat_cmp(cfmtime, &statbuf)
01620 && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
01621 int unchanged = 1;
01622
01623
01624 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
01625 if (!config_text_file_load(NULL, NULL, cfinclude->include,
01626 NULL, flags, "", who_asked)) {
01627
01628 unchanged = 0;
01629 break;
01630 }
01631 }
01632
01633 if (unchanged) {
01634 AST_LIST_UNLOCK(&cfmtime_head);
01635 #ifdef AST_INCLUDE_GLOB
01636 globfree(&globbuf);
01637 #endif
01638 ast_free(comment_buffer);
01639 ast_free(lline_buffer);
01640 return CONFIG_STATUS_FILEUNCHANGED;
01641 }
01642 }
01643
01644
01645 if (cfg == NULL) {
01646 if (cfmtime) {
01647 AST_LIST_UNLOCK(&cfmtime_head);
01648 }
01649 continue;
01650 }
01651
01652 if (cfmtime) {
01653
01654 cfmtime->has_exec = 0;
01655 config_cache_flush_includes(cfmtime);
01656
01657 cfmstat_save(cfmtime, &statbuf);
01658 AST_LIST_UNLOCK(&cfmtime_head);
01659 }
01660
01661 ast_verb(2, "Parsing '%s': ", fn);
01662 fflush(stdout);
01663 if (!(f = fopen(fn, "r"))) {
01664 ast_debug(1, "No file to parse: %s\n", fn);
01665 ast_verb(2, "Not found (%s)\n", strerror(errno));
01666 continue;
01667 }
01668 count++;
01669
01670 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
01671 ast_debug(1, "Parsing %s\n", fn);
01672 ast_verb(2, "Found\n");
01673 while (!feof(f)) {
01674 lineno++;
01675 if (fgets(buf, sizeof(buf), f)) {
01676
01677 if (strlen(buf) == sizeof(buf) - 1 && buf[sizeof(buf) - 1] != '\n') {
01678 ast_log(LOG_WARNING, "Line %d too long, skipping. It begins with: %.32s...\n", lineno, buf);
01679 while (fgets(buf, sizeof(buf), f)) {
01680 if (strlen(buf) != sizeof(buf) - 1 || buf[sizeof(buf) - 1] == '\n') {
01681 break;
01682 }
01683 }
01684 continue;
01685 }
01686
01687 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && lline_buffer && ast_str_strlen(lline_buffer)) {
01688 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));
01689 ast_str_reset(lline_buffer);
01690 }
01691
01692 new_buf = buf;
01693 if (comment)
01694 process_buf = NULL;
01695 else
01696 process_buf = buf;
01697
01698 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"))) {
01699
01700 CB_ADD(&comment_buffer, "\n");
01701 continue;
01702 }
01703
01704 while ((comment_p = strchr(new_buf, COMMENT_META))) {
01705 if ((comment_p > new_buf) && (*(comment_p - 1) == '\\')) {
01706
01707 new_buf = comment_p;
01708
01709 memmove(comment_p - 1, comment_p, strlen(comment_p) + 1);
01710 } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
01711
01712 if (comment < MAX_NESTED_COMMENTS) {
01713 *comment_p = '\0';
01714 new_buf = comment_p + 3;
01715 comment++;
01716 nest[comment-1] = lineno;
01717 } else {
01718 ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
01719 }
01720 } else if ((comment_p >= new_buf + 2) &&
01721 (*(comment_p - 1) == COMMENT_TAG) &&
01722 (*(comment_p - 2) == COMMENT_TAG)) {
01723
01724 comment--;
01725 new_buf = comment_p + 1;
01726 if (!comment) {
01727
01728 if (process_buf) {
01729
01730 char *oldptr;
01731 oldptr = process_buf + strlen(process_buf);
01732 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01733 CB_ADD(&comment_buffer, ";");
01734 CB_ADD_LEN(&comment_buffer, oldptr+1, new_buf-oldptr-1);
01735 }
01736
01737 memmove(oldptr, new_buf, strlen(new_buf) + 1);
01738 new_buf = oldptr;
01739 } else
01740 process_buf = new_buf;
01741 }
01742 } else {
01743 if (!comment) {
01744
01745
01746 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
01747 CB_ADD(&lline_buffer, comment_p);
01748 }
01749 *comment_p = '\0';
01750 new_buf = comment_p;
01751 } else
01752 new_buf = comment_p + 1;
01753 }
01754 }
01755 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf ) {
01756 CB_ADD(&comment_buffer, buf);
01757 }
01758
01759 if (process_buf) {
01760 char *buffer = ast_strip(process_buf);
01761 if (!ast_strlen_zero(buffer)) {
01762 if (process_text_line(cfg, &cat, buffer, lineno, fn, flags, comment_buffer, lline_buffer, suggested_include_file, &last_cat, &last_var, who_asked)) {
01763 cfg = CONFIG_STATUS_FILEINVALID;
01764 break;
01765 }
01766 }
01767 }
01768 }
01769 }
01770
01771 if (last_cat) {
01772 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01773 if (lline_buffer && ast_str_strlen(lline_buffer)) {
01774 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));
01775 ast_str_reset(lline_buffer);
01776 }
01777 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
01778 }
01779 } else if (last_var) {
01780 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01781 if (lline_buffer && ast_str_strlen(lline_buffer)) {
01782 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer));
01783 ast_str_reset(lline_buffer);
01784 }
01785 last_var->trailing = ALLOC_COMMENT(comment_buffer);
01786 }
01787 } else {
01788 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
01789 ast_debug(1, "Nothing to attach comments to, discarded: %s\n", ast_str_buffer(comment_buffer));
01790 }
01791 }
01792 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
01793 CB_RESET(comment_buffer, lline_buffer);
01794
01795 fclose(f);
01796 } while (0);
01797 if (comment) {
01798 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
01799 }
01800 #ifdef AST_INCLUDE_GLOB
01801 if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
01802 break;
01803 }
01804 }
01805 globfree(&globbuf);
01806 }
01807 }
01808 #endif
01809
01810 ast_free(comment_buffer);
01811 ast_free(lline_buffer);
01812
01813 if (count == 0)
01814 return NULL;
01815
01816 return cfg;
01817 }
01818
01819
01820
01821
01822
01823
01824
01825
01826
01827
01828
01829
01830
01831
01832
01833
01834
01835
01836
01837
01838
01839
01840 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
01841 {
01842 char date[256]="";
01843 time_t t;
01844
01845 time(&t);
01846 ast_copy_string(date, ctime(&t), sizeof(date));
01847
01848 fprintf(f1, ";!\n");
01849 fprintf(f1, ";! Automatically generated configuration file\n");
01850 if (strcmp(configfile, fn))
01851 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
01852 else
01853 fprintf(f1, ";! Filename: %s\n", configfile);
01854 fprintf(f1, ";! Generator: %s\n", generator);
01855 fprintf(f1, ";! Creation Date: %s", date);
01856 fprintf(f1, ";!\n");
01857 }
01858
01859 static void inclfile_destroy(void *obj)
01860 {
01861 const struct inclfile *o = obj;
01862
01863 ast_free(o->fname);
01864 }
01865
01866 static void make_fn(char *fn, size_t fn_size, const char *file, const char *configfile)
01867 {
01868 if (ast_strlen_zero(file)) {
01869 if (configfile[0] == '/') {
01870 ast_copy_string(fn, configfile, fn_size);
01871 } else {
01872 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
01873 }
01874 } else if (file[0] == '/') {
01875 ast_copy_string(fn, file, fn_size);
01876 } else {
01877 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
01878 }
01879 }
01880
01881 static struct inclfile *set_fn(char *fn, size_t fn_size, const char *file, const char *configfile, struct ao2_container *fileset)
01882 {
01883 struct inclfile lookup;
01884 struct inclfile *fi;
01885
01886 make_fn(fn, fn_size, file, configfile);
01887 lookup.fname = fn;
01888 fi = ao2_find(fileset, &lookup, OBJ_POINTER);
01889 if (fi) {
01890
01891 return fi;
01892 }
01893
01894
01895 fi = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
01896 if (!fi) {
01897
01898 return NULL;
01899 }
01900 fi->fname = ast_strdup(fn);
01901 if (!fi->fname) {
01902
01903 ao2_ref(fi, -1);
01904 return NULL;
01905 }
01906 fi->lineno = 1;
01907
01908 ao2_link(fileset, fi);
01909
01910 return fi;
01911 }
01912
01913 static int count_linefeeds(char *str)
01914 {
01915 int count = 0;
01916
01917 while (*str) {
01918 if (*str =='\n')
01919 count++;
01920 str++;
01921 }
01922 return count;
01923 }
01924
01925 static int count_linefeeds_in_comments(struct ast_comment *x)
01926 {
01927 int count = 0;
01928
01929 while (x) {
01930 count += count_linefeeds(x->cmt);
01931 x = x->next;
01932 }
01933 return count;
01934 }
01935
01936 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
01937 {
01938 int precomment_lines;
01939 int i;
01940
01941 if (!fi) {
01942
01943 return;
01944 }
01945
01946 precomment_lines = count_linefeeds_in_comments(precomments);
01947
01948
01949
01950
01951
01952 if (lineno - precomment_lines - fi->lineno < 0) {
01953 return;
01954 } else if (lineno == 0) {
01955
01956 return;
01957 } else if (lineno - precomment_lines - fi->lineno < 5) {
01958
01959
01960 for (i = fi->lineno; i < lineno - precomment_lines; i++) {
01961 fprintf(fp, "\n");
01962 }
01963 } else {
01964
01965
01966 fprintf(fp, "\n");
01967 }
01968
01969 fi->lineno = lineno + 1;
01970 }
01971
01972 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
01973 {
01974 return ast_config_text_file_save(configfile, cfg, generator);
01975 }
01976
01977 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
01978 {
01979 FILE *f;
01980 char fn[PATH_MAX];
01981 struct ast_variable *var;
01982 struct ast_category *cat;
01983 struct ast_comment *cmt;
01984 struct ast_config_include *incl;
01985 int blanklines = 0;
01986 struct ao2_container *fileset;
01987 struct inclfile *fi;
01988
01989 fileset = ao2_container_alloc(1023, hash_string, hashtab_compare_strings);
01990 if (!fileset) {
01991
01992 return -1;
01993 }
01994
01995
01996 for (incl = cfg->includes; incl; incl = incl->next) {
01997
01998 incl->output = 0;
01999
02000 if (!incl->exec) {
02001 make_fn(fn, sizeof(fn), incl->included_file, configfile);
02002 if (access(fn, R_OK | W_OK)) {
02003 ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
02004 return -1;
02005 }
02006 }
02007 }
02008
02009
02010 make_fn(fn, sizeof(fn), 0, configfile);
02011 if (access(fn, R_OK | W_OK)) {
02012 ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
02013 return -1;
02014 }
02015
02016
02017
02018
02019
02020 for (incl = cfg->includes; incl; incl = incl->next) {
02021 if (!incl->exec) {
02022
02023 fi = set_fn(fn, sizeof(fn), incl->included_file, configfile, fileset);
02024 f = fopen(fn, "w");
02025 if (f) {
02026 gen_header(f, configfile, fn, generator);
02027 fclose(f);
02028 } else {
02029 ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
02030 }
02031 if (fi) {
02032 ao2_ref(fi, -1);
02033 }
02034 }
02035 }
02036
02037
02038 fi = set_fn(fn, sizeof(fn), 0, configfile, fileset);
02039 if (
02040 #ifdef __CYGWIN__
02041 (f = fopen(fn, "w+"))
02042 #else
02043 (f = fopen(fn, "w"))
02044 #endif
02045 ) {
02046 ast_verb(2, "Saving '%s': ", fn);
02047 gen_header(f, configfile, fn, generator);
02048 cat = cfg->root;
02049 fclose(f);
02050 if (fi) {
02051 ao2_ref(fi, -1);
02052 }
02053
02054
02055
02056
02057
02058 while (cat) {
02059 fi = set_fn(fn, sizeof(fn), cat->file, configfile, fileset);
02060 f = fopen(fn, "a");
02061 if (!f) {
02062 ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
02063 if (fi) {
02064 ao2_ref(fi, -1);
02065 }
02066 ao2_ref(fileset, -1);
02067 return -1;
02068 }
02069
02070
02071 for (incl=cfg->includes; incl; incl = incl->next) {
02072 if (strcmp(incl->include_location_file, cat->file) == 0){
02073 if (cat->lineno > incl->include_location_lineno && !incl->output) {
02074 if (incl->exec)
02075 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
02076 else
02077 fprintf(f,"#include \"%s\"\n", incl->included_file);
02078 incl->output = 1;
02079 }
02080 }
02081 }
02082
02083 insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
02084
02085 for (cmt = cat->precomments; cmt; cmt=cmt->next) {
02086 char *cmtp = cmt->cmt;
02087 while (cmtp && *cmtp == ';' && *(cmtp+1) == '!') {
02088 char *cmtp2 = strchr(cmtp+1, '\n');
02089 if (cmtp2)
02090 cmtp = cmtp2+1;
02091 else cmtp = 0;
02092 }
02093 if (cmtp)
02094 fprintf(f,"%s", cmtp);
02095 }
02096 fprintf(f, "[%s]", cat->name);
02097 if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
02098 fprintf(f, "(");
02099 if (cat->ignored) {
02100 fprintf(f, "!");
02101 }
02102 if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
02103 fprintf(f, ",");
02104 }
02105 if (!AST_LIST_EMPTY(&cat->template_instances)) {
02106 struct ast_category_template_instance *x;
02107 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
02108 fprintf(f,"%s",x->name);
02109 if (x != AST_LIST_LAST(&cat->template_instances))
02110 fprintf(f,",");
02111 }
02112 }
02113 fprintf(f, ")");
02114 }
02115 for(cmt = cat->sameline; cmt; cmt=cmt->next)
02116 {
02117 fprintf(f,"%s", cmt->cmt);
02118 }
02119 if (!cat->sameline)
02120 fprintf(f,"\n");
02121 for (cmt = cat->trailing; cmt; cmt=cmt->next) {
02122 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
02123 fprintf(f,"%s", cmt->cmt);
02124 }
02125 fclose(f);
02126 if (fi) {
02127 ao2_ref(fi, -1);
02128 }
02129
02130 var = cat->root;
02131 while (var) {
02132 struct ast_category_template_instance *x;
02133 int found = 0;
02134 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
02135 struct ast_variable *v;
02136 for (v = x->inst->root; v; v = v->next) {
02137 if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
02138 found = 1;
02139 break;
02140 }
02141 }
02142 if (found)
02143 break;
02144 }
02145 if (found) {
02146 var = var->next;
02147 continue;
02148 }
02149 fi = set_fn(fn, sizeof(fn), var->file, configfile, fileset);
02150 f = fopen(fn, "a");
02151 if (!f) {
02152 ast_debug(1, "Unable to open for writing: %s\n", fn);
02153 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
02154 if (fi) {
02155 ao2_ref(fi, -1);
02156 }
02157 ao2_ref(fileset, -1);
02158 return -1;
02159 }
02160
02161
02162 for (incl=cfg->includes; incl; incl = incl->next) {
02163 if (strcmp(incl->include_location_file, var->file) == 0){
02164 if (var->lineno > incl->include_location_lineno && !incl->output) {
02165 if (incl->exec)
02166 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
02167 else
02168 fprintf(f,"#include \"%s\"\n", incl->included_file);
02169 incl->output = 1;
02170 }
02171 }
02172 }
02173
02174 insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
02175 for (cmt = var->precomments; cmt; cmt=cmt->next) {
02176 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
02177 fprintf(f,"%s", cmt->cmt);
02178 }
02179 if (var->sameline)
02180 fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
02181 else
02182 fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
02183 for (cmt = var->trailing; cmt; cmt=cmt->next) {
02184 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
02185 fprintf(f,"%s", cmt->cmt);
02186 }
02187 if (var->blanklines) {
02188 blanklines = var->blanklines;
02189 while (blanklines--)
02190 fprintf(f, "\n");
02191 }
02192
02193 fclose(f);
02194 if (fi) {
02195 ao2_ref(fi, -1);
02196 }
02197
02198 var = var->next;
02199 }
02200 cat = cat->next;
02201 }
02202 if (!option_debug)
02203 ast_verb(2, "Saved\n");
02204 } else {
02205 ast_debug(1, "Unable to open for writing: %s\n", fn);
02206 ast_verb(2, "Unable to write (%s)", strerror(errno));
02207 if (fi) {
02208 ao2_ref(fi, -1);
02209 }
02210 ao2_ref(fileset, -1);
02211 return -1;
02212 }
02213
02214
02215
02216 for (incl=cfg->includes; incl; incl = incl->next) {
02217 if (!incl->output) {
02218
02219 fi = set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset);
02220 f = fopen(fn, "a");
02221 if (!f) {
02222 ast_debug(1, "Unable to open for writing: %s\n", fn);
02223 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
02224 if (fi) {
02225 ao2_ref(fi, -1);
02226 }
02227 ao2_ref(fileset, -1);
02228 return -1;
02229 }
02230
02231
02232 if (incl->exec)
02233 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
02234 else
02235 fprintf(f,"#include \"%s\"\n", incl->included_file);
02236 fclose(f);
02237 incl->output = 1;
02238 if (fi) {
02239 ao2_ref(fi, -1);
02240 }
02241 }
02242 }
02243 ao2_ref(fileset, -1);
02244
02245 return 0;
02246 }
02247
02248 static void clear_config_maps(void)
02249 {
02250 struct ast_config_map *map;
02251
02252 ast_mutex_lock(&config_lock);
02253
02254 while (config_maps) {
02255 map = config_maps;
02256 config_maps = config_maps->next;
02257 ast_free(map);
02258 }
02259
02260 ast_mutex_unlock(&config_lock);
02261 }
02262
02263 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
02264 {
02265 struct ast_config_map *map;
02266 char *dst;
02267 int length;
02268
02269 length = sizeof(*map);
02270 length += strlen(name) + 1;
02271 length += strlen(driver) + 1;
02272 length += strlen(database) + 1;
02273 if (table)
02274 length += strlen(table) + 1;
02275
02276 if (!(map = ast_calloc(1, length)))
02277 return -1;
02278
02279 dst = map->stuff;
02280 map->name = strcpy(dst, name);
02281 dst += strlen(dst) + 1;
02282 map->driver = strcpy(dst, driver);
02283 dst += strlen(dst) + 1;
02284 map->database = strcpy(dst, database);
02285 if (table) {
02286 dst += strlen(dst) + 1;
02287 map->table = strcpy(dst, table);
02288 }
02289 map->priority = priority;
02290 map->next = config_maps;
02291 config_maps = map;
02292
02293 ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
02294
02295 return 0;
02296 }
02297
02298 int read_config_maps(void)
02299 {
02300 struct ast_config *config, *configtmp;
02301 struct ast_variable *v;
02302 char *driver, *table, *database, *textpri, *stringp, *tmp;
02303 struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
02304 int pri;
02305
02306 clear_config_maps();
02307
02308 configtmp = ast_config_new();
02309 if (!configtmp) {
02310 ast_log(LOG_ERROR, "Unable to allocate memory for new config\n");
02311 return -1;
02312 }
02313 config = ast_config_internal_load(extconfig_conf, configtmp, flags, "", "extconfig");
02314 if (config == CONFIG_STATUS_FILEINVALID) {
02315 return -1;
02316 } else if (!config) {
02317 ast_config_destroy(configtmp);
02318 return 0;
02319 }
02320
02321 for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
02322 char buf[512];
02323 ast_copy_string(buf, v->value, sizeof(buf));
02324 stringp = buf;
02325 driver = strsep(&stringp, ",");
02326
02327 if ((tmp = strchr(stringp, '\"')))
02328 stringp = tmp;
02329
02330
02331 if (*stringp == '"') {
02332 stringp++;
02333 database = strsep(&stringp, "\"");
02334 strsep(&stringp, ",");
02335 } else {
02336
02337 database = strsep(&stringp, ",");
02338 }
02339
02340 table = strsep(&stringp, ",");
02341 textpri = strsep(&stringp, ",");
02342 if (!textpri || !(pri = atoi(textpri))) {
02343 pri = 1;
02344 }
02345
02346 if (!strcmp(v->name, extconfig_conf)) {
02347 ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
02348 continue;
02349 }
02350
02351 if (!strcmp(v->name, "asterisk.conf")) {
02352 ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
02353 continue;
02354 }
02355
02356 if (!strcmp(v->name, "logger.conf")) {
02357 ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
02358 continue;
02359 }
02360
02361 if (!driver || !database)
02362 continue;
02363 if (!strcasecmp(v->name, "sipfriends")) {
02364 ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sippeers instead.\n");
02365 append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri);
02366 } else if (!strcasecmp(v->name, "iaxfriends")) {
02367 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");
02368 append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri);
02369 append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri);
02370 } else
02371 append_mapping(v->name, driver, database, table, pri);
02372 }
02373
02374 ast_config_destroy(config);
02375 return 0;
02376 }
02377
02378 int ast_config_engine_register(struct ast_config_engine *new)
02379 {
02380 struct ast_config_engine *ptr;
02381
02382 ast_mutex_lock(&config_lock);
02383
02384 if (!config_engine_list) {
02385 config_engine_list = new;
02386 } else {
02387 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
02388 ptr->next = new;
02389 }
02390
02391 ast_mutex_unlock(&config_lock);
02392 ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
02393
02394 return 1;
02395 }
02396
02397 int ast_config_engine_deregister(struct ast_config_engine *del)
02398 {
02399 struct ast_config_engine *ptr, *last=NULL;
02400
02401 ast_mutex_lock(&config_lock);
02402
02403 for (ptr = config_engine_list; ptr; ptr=ptr->next) {
02404 if (ptr == del) {
02405 if (last)
02406 last->next = ptr->next;
02407 else
02408 config_engine_list = ptr->next;
02409 break;
02410 }
02411 last = ptr;
02412 }
02413
02414 ast_mutex_unlock(&config_lock);
02415
02416 return 0;
02417 }
02418
02419
02420 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz)
02421 {
02422 struct ast_config_engine *eng, *ret = NULL;
02423 struct ast_config_map *map;
02424
02425 ast_mutex_lock(&config_lock);
02426
02427 for (map = config_maps; map; map = map->next) {
02428 if (!strcasecmp(family, map->name) && (priority == map->priority)) {
02429 if (database)
02430 ast_copy_string(database, map->database, dbsiz);
02431 if (table)
02432 ast_copy_string(table, map->table ? map->table : family, tabsiz);
02433 break;
02434 }
02435 }
02436
02437
02438 if (map) {
02439 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
02440 if (!strcasecmp(eng->name, map->driver))
02441 ret = eng;
02442 }
02443 }
02444
02445 ast_mutex_unlock(&config_lock);
02446
02447
02448 if (map && !ret)
02449 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
02450
02451 return ret;
02452 }
02453
02454 static struct ast_config_engine text_file_engine = {
02455 .name = "text",
02456 .load_func = config_text_file_load,
02457 };
02458
02459 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)
02460 {
02461 char db[256];
02462 char table[256];
02463 struct ast_config_engine *loader = &text_file_engine;
02464 struct ast_config *result;
02465
02466
02467 if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
02468 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
02469 return NULL;
02470 }
02471
02472 cfg->include_level++;
02473
02474 if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
02475 struct ast_config_engine *eng;
02476
02477 eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
02478
02479
02480 if (eng && eng->load_func) {
02481 loader = eng;
02482 } else {
02483 eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
02484 if (eng && eng->load_func)
02485 loader = eng;
02486 }
02487 }
02488
02489 result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
02490
02491 if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
02492 result->include_level--;
02493 else if (result != CONFIG_STATUS_FILEINVALID)
02494 cfg->include_level--;
02495
02496 return result;
02497 }
02498
02499 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
02500 {
02501 struct ast_config *cfg;
02502 struct ast_config *result;
02503
02504 cfg = ast_config_new();
02505 if (!cfg)
02506 return NULL;
02507
02508 result = ast_config_internal_load(filename, cfg, flags, "", who_asked);
02509 if (!result || result == CONFIG_STATUS_FILEUNCHANGED || result == CONFIG_STATUS_FILEINVALID)
02510 ast_config_destroy(cfg);
02511
02512 return result;
02513 }
02514
02515 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
02516 {
02517 struct ast_config_engine *eng;
02518 char db[256];
02519 char table[256];
02520 struct ast_variable *res=NULL;
02521 int i;
02522
02523 for (i = 1; ; i++) {
02524 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02525 if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) {
02526 return res;
02527 }
02528 } else {
02529 return NULL;
02530 }
02531 }
02532
02533 return res;
02534 }
02535
02536 struct ast_variable *ast_load_realtime_all(const char *family, ...)
02537 {
02538 struct ast_variable *res;
02539 va_list ap;
02540
02541 va_start(ap, family);
02542 res = ast_load_realtime_helper(family, ap);
02543 va_end(ap);
02544
02545 return res;
02546 }
02547
02548 struct ast_variable *ast_load_realtime(const char *family, ...)
02549 {
02550 struct ast_variable *res;
02551 struct ast_variable *cur;
02552 struct ast_variable **prev;
02553 va_list ap;
02554
02555 va_start(ap, family);
02556 res = ast_load_realtime_helper(family, ap);
02557 va_end(ap);
02558
02559
02560 prev = &res;
02561 cur = res;
02562 while (cur) {
02563 if (ast_strlen_zero(cur->value)) {
02564
02565 struct ast_variable *next;
02566
02567 next = cur->next;
02568 *prev = next;
02569 ast_variable_destroy(cur);
02570 cur = next;
02571 } else {
02572
02573 if (cur->value[0] == ' ' && cur->value[1] == '\0') {
02574 char *vptr = (char *) cur->value;
02575
02576 vptr[0] = '\0';
02577 }
02578
02579 prev = &cur->next;
02580 cur = cur->next;
02581 }
02582 }
02583 return res;
02584 }
02585
02586
02587 int ast_check_realtime(const char *family)
02588 {
02589 struct ast_config_engine *eng;
02590 if (!ast_realtime_enabled()) {
02591 return 0;
02592 }
02593
02594 eng = find_engine(family, 1, NULL, 0, NULL, 0);
02595 if (eng)
02596 return 1;
02597 return 0;
02598 }
02599
02600
02601 int ast_realtime_enabled(void)
02602 {
02603 return config_maps ? 1 : 0;
02604 }
02605
02606 int ast_realtime_require_field(const char *family, ...)
02607 {
02608 struct ast_config_engine *eng;
02609 char db[256];
02610 char table[256];
02611 va_list ap;
02612 int res = -1, i;
02613
02614 va_start(ap, family);
02615 for (i = 1; ; i++) {
02616 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02617
02618 if (eng->require_func && !(res = eng->require_func(db, table, ap))) {
02619 break;
02620 }
02621 } else {
02622 break;
02623 }
02624 }
02625 va_end(ap);
02626
02627 return res;
02628 }
02629
02630 int ast_unload_realtime(const char *family)
02631 {
02632 struct ast_config_engine *eng;
02633 char db[256];
02634 char table[256];
02635 int res = -1, i;
02636
02637 for (i = 1; ; i++) {
02638 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02639 if (eng->unload_func) {
02640
02641 res = eng->unload_func(db, table);
02642 }
02643 } else {
02644 break;
02645 }
02646 }
02647 return res;
02648 }
02649
02650 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
02651 {
02652 struct ast_config_engine *eng;
02653 char db[256];
02654 char table[256];
02655 struct ast_config *res = NULL;
02656 va_list ap;
02657 int i;
02658
02659 va_start(ap, family);
02660 for (i = 1; ; i++) {
02661 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02662 if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) {
02663
02664 if (!res->root) {
02665 ast_config_destroy(res);
02666 res = NULL;
02667 }
02668 break;
02669 }
02670 } else {
02671 break;
02672 }
02673 }
02674 va_end(ap);
02675
02676 return res;
02677 }
02678
02679 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
02680 {
02681 struct ast_config_engine *eng;
02682 int res = -1, i;
02683 char db[256];
02684 char table[256];
02685 va_list ap;
02686
02687 va_start(ap, lookup);
02688 for (i = 1; ; i++) {
02689 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02690
02691 if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) {
02692 break;
02693 }
02694 } else {
02695 break;
02696 }
02697 }
02698 va_end(ap);
02699
02700 return res;
02701 }
02702
02703 int ast_update2_realtime(const char *family, ...)
02704 {
02705 struct ast_config_engine *eng;
02706 int res = -1, i;
02707 char db[256];
02708 char table[256];
02709 va_list ap;
02710
02711 va_start(ap, family);
02712 for (i = 1; ; i++) {
02713 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02714 if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) {
02715 break;
02716 }
02717 } else {
02718 break;
02719 }
02720 }
02721 va_end(ap);
02722
02723 return res;
02724 }
02725
02726 int ast_store_realtime(const char *family, ...)
02727 {
02728 struct ast_config_engine *eng;
02729 int res = -1, i;
02730 char db[256];
02731 char table[256];
02732 va_list ap;
02733
02734 va_start(ap, family);
02735 for (i = 1; ; i++) {
02736 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02737
02738 if (eng->store_func && !(res = eng->store_func(db, table, ap))) {
02739 break;
02740 }
02741 } else {
02742 break;
02743 }
02744 }
02745 va_end(ap);
02746
02747 return res;
02748 }
02749
02750 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
02751 {
02752 struct ast_config_engine *eng;
02753 int res = -1, i;
02754 char db[256];
02755 char table[256];
02756 va_list ap;
02757
02758 va_start(ap, lookup);
02759 for (i = 1; ; i++) {
02760 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
02761 if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) {
02762 break;
02763 }
02764 } else {
02765 break;
02766 }
02767 }
02768 va_end(ap);
02769
02770 return res;
02771 }
02772
02773 char *ast_realtime_decode_chunk(char *chunk)
02774 {
02775 char *orig = chunk;
02776 for (; *chunk; chunk++) {
02777 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
02778 sscanf(chunk + 1, "%02hhX", (unsigned char *)chunk);
02779 memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
02780 }
02781 }
02782 return orig;
02783 }
02784
02785 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
02786 {
02787 if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
02788 ast_str_set(dest, maxlen, "%s", chunk);
02789 } else {
02790 ast_str_reset(*dest);
02791 for (; *chunk; chunk++) {
02792 if (strchr(";^", *chunk)) {
02793 ast_str_append(dest, maxlen, "^%02hhX", *chunk);
02794 } else {
02795 ast_str_append(dest, maxlen, "%c", *chunk);
02796 }
02797 }
02798 }
02799 return ast_str_buffer(*dest);
02800 }
02801
02802
02803
02804
02805 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
02806 void *p_result, ...)
02807 {
02808 va_list ap;
02809 int error = 0;
02810
02811 va_start(ap, p_result);
02812 switch (flags & PARSE_TYPE) {
02813 case PARSE_INT32:
02814 {
02815 long int x = 0;
02816 int32_t *result = p_result;
02817 int32_t def = result ? *result : 0, high = INT32_MAX, low = INT32_MIN;
02818 char *endptr = NULL;
02819
02820
02821 if (flags & PARSE_DEFAULT) {
02822 def = va_arg(ap, int32_t);
02823 }
02824 if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) {
02825 low = va_arg(ap, int32_t);
02826 high = va_arg(ap, int32_t);
02827 }
02828 if (ast_strlen_zero(arg)) {
02829 error = 1;
02830 goto int32_done;
02831 }
02832 errno = 0;
02833 x = strtol(arg, &endptr, 0);
02834 if (*endptr || errno || x < INT32_MIN || x > INT32_MAX) {
02835
02836 error = 1;
02837 goto int32_done;
02838 }
02839 error = (x < low) || (x > high);
02840 if (flags & PARSE_OUT_RANGE) {
02841 error = !error;
02842 }
02843 int32_done:
02844 if (result) {
02845 *result = error ? def : x;
02846 }
02847
02848 ast_debug(3, "extract int from [%s] in [%d, %d] gives [%ld](%d)\n",
02849 arg, low, high, result ? *result : x, error);
02850 break;
02851 }
02852
02853 case PARSE_UINT32:
02854 {
02855 unsigned long int x = 0;
02856 uint32_t *result = p_result;
02857 uint32_t def = result ? *result : 0, low = 0, high = UINT32_MAX;
02858 char *endptr = NULL;
02859
02860
02861 if (flags & PARSE_DEFAULT) {
02862 def = va_arg(ap, uint32_t);
02863 }
02864 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
02865
02866 low = va_arg(ap, uint32_t);
02867 high = va_arg(ap, uint32_t);
02868 }
02869
02870 if (ast_strlen_zero(arg)) {
02871 error = 1;
02872 goto uint32_done;
02873 }
02874
02875 arg = ast_skip_blanks(arg);
02876 if (*arg == '-') {
02877 error = 1;
02878 goto uint32_done;
02879 }
02880 errno = 0;
02881 x = strtoul(arg, &endptr, 0);
02882 if (*endptr || errno || x > UINT32_MAX) {
02883 error = 1;
02884 goto uint32_done;
02885 }
02886 error = (x < low) || (x > high);
02887 if (flags & PARSE_OUT_RANGE) {
02888 error = !error;
02889 }
02890 uint32_done:
02891 if (result) {
02892 *result = error ? def : x;
02893 }
02894 ast_debug(3, "extract uint from [%s] in [%u, %u] gives [%lu](%d)\n",
02895 arg, low, high, result ? *result : x, error);
02896 break;
02897 }
02898
02899 case PARSE_DOUBLE:
02900 {
02901 double *result = p_result;
02902 double x = 0, def = result ? *result : 0, low = -HUGE_VAL, high = HUGE_VAL;
02903 char *endptr = NULL;
02904
02905
02906 if (flags & PARSE_DEFAULT) {
02907 def = va_arg(ap, double);
02908 }
02909 if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) {
02910
02911 low = va_arg(ap, double);
02912 high = va_arg(ap, double);
02913 }
02914 if (ast_strlen_zero(arg)) {
02915 error = 1;
02916 goto double_done;
02917 }
02918 errno = 0;
02919 x = strtod(arg, &endptr);
02920 if (*endptr || errno == ERANGE) {
02921 error = 1;
02922 goto double_done;
02923 }
02924 error = (x < low) || (x > high);
02925 if (flags & PARSE_OUT_RANGE) {
02926 error = !error;
02927 }
02928 double_done:
02929 if (result) {
02930 *result = error ? def : x;
02931 }
02932 ast_debug(3, "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
02933 arg, low, high, result ? *result : x, error);
02934 break;
02935 }
02936 case PARSE_ADDR:
02937 {
02938 struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
02939
02940 if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
02941 error = 1;
02942 }
02943
02944 ast_debug(3, "extract addr from %s gives %s(%d)\n",
02945 arg, ast_sockaddr_stringify(addr), error);
02946
02947 break;
02948 }
02949 case PARSE_INADDR:
02950 {
02951 char *port, *buf;
02952 struct sockaddr_in _sa_buf;
02953 struct sockaddr_in *sa = p_result ?
02954 (struct sockaddr_in *)p_result : &_sa_buf;
02955
02956 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
02957 va_arg(ap, struct sockaddr_in *) : sa;
02958 struct hostent *hp;
02959 struct ast_hostent ahp;
02960
02961 memset(&_sa_buf, '\0', sizeof(_sa_buf));
02962
02963 port = ast_strdupa(arg);
02964 buf = strsep(&port, ":");
02965 sa->sin_family = AF_INET;
02966
02967
02968
02969
02970 flags &= PARSE_PORT_MASK;
02971 if (port) {
02972 if (flags == PARSE_PORT_FORBID) {
02973 error = 1;
02974 sa->sin_port = def->sin_port;
02975 } else if (flags == PARSE_PORT_IGNORE)
02976 sa->sin_port = def->sin_port;
02977 else
02978 sa->sin_port = htons(strtol(port, NULL, 0));
02979 } else {
02980 sa->sin_port = def->sin_port;
02981 if (flags == PARSE_PORT_REQUIRE)
02982 error = 1;
02983 }
02984
02985 hp = ast_gethostbyname(buf, &ahp);
02986 if (hp)
02987 memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
02988 else {
02989 error = 1;
02990 sa->sin_addr = def->sin_addr;
02991 }
02992 ast_debug(3,
02993 "extract inaddr from [%s] gives [%s:%d](%d)\n",
02994 arg, ast_inet_ntoa(sa->sin_addr),
02995 ntohs(sa->sin_port), error);
02996 break;
02997 }
02998 }
02999 va_end(ap);
03000 return error;
03001 }
03002
03003 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03004 {
03005 struct ast_config_engine *eng;
03006 struct ast_config_map *map;
03007
03008 switch (cmd) {
03009 case CLI_INIT:
03010 e->command = "core show config mappings";
03011 e->usage =
03012 "Usage: core show config mappings\n"
03013 " Shows the filenames to config engines.\n";
03014 return NULL;
03015 case CLI_GENERATE:
03016 return NULL;
03017 }
03018
03019 ast_mutex_lock(&config_lock);
03020
03021 if (!config_engine_list) {
03022 ast_cli(a->fd, "No config mappings found.\n");
03023 } else {
03024 for (eng = config_engine_list; eng; eng = eng->next) {
03025 ast_cli(a->fd, "Config Engine: %s\n", eng->name);
03026 for (map = config_maps; map; map = map->next) {
03027 if (!strcasecmp(map->driver, eng->name)) {
03028 ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
03029 map->table ? map->table : map->name);
03030 }
03031 }
03032 }
03033 }
03034
03035 ast_mutex_unlock(&config_lock);
03036
03037 return CLI_SUCCESS;
03038 }
03039
03040 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03041 {
03042 struct cache_file_mtime *cfmtime;
03043 char *prev = "", *completion_value = NULL;
03044 int wordlen, which = 0;
03045
03046 switch (cmd) {
03047 case CLI_INIT:
03048 e->command = "config reload";
03049 e->usage =
03050 "Usage: config reload <filename.conf>\n"
03051 " Reloads all modules that reference <filename.conf>\n";
03052 return NULL;
03053 case CLI_GENERATE:
03054 if (a->pos > 2) {
03055 return NULL;
03056 }
03057
03058 wordlen = strlen(a->word);
03059
03060 AST_LIST_LOCK(&cfmtime_head);
03061 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
03062
03063 if (strcmp(cfmtime->filename, prev) == 0) {
03064 continue;
03065 }
03066
03067
03068 if (ast_strlen_zero(cfmtime->who_asked)) {
03069 continue;
03070 }
03071
03072 if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
03073 completion_value = ast_strdup(cfmtime->filename);
03074 break;
03075 }
03076
03077
03078 prev = cfmtime->filename;
03079 }
03080 AST_LIST_UNLOCK(&cfmtime_head);
03081
03082 return completion_value;
03083 }
03084
03085 if (a->argc != 3) {
03086 return CLI_SHOWUSAGE;
03087 }
03088
03089 AST_LIST_LOCK(&cfmtime_head);
03090 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
03091 if (!strcmp(cfmtime->filename, a->argv[2])) {
03092 char *buf = ast_alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1);
03093 sprintf(buf, "module reload %s", cfmtime->who_asked);
03094 ast_cli_command(a->fd, buf);
03095 }
03096 }
03097 AST_LIST_UNLOCK(&cfmtime_head);
03098
03099 return CLI_SUCCESS;
03100 }
03101
03102 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03103 {
03104 struct cache_file_mtime *cfmtime;
03105
03106 switch (cmd) {
03107 case CLI_INIT:
03108 e->command = "config list";
03109 e->usage =
03110 "Usage: config list\n"
03111 " Show all modules that have loaded a configuration file\n";
03112 return NULL;
03113 case CLI_GENERATE:
03114 return NULL;
03115 }
03116
03117 AST_LIST_LOCK(&cfmtime_head);
03118 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
03119 ast_cli(a->fd, "%-20.20s %-50s\n", S_OR(cfmtime->who_asked, "core"), cfmtime->filename);
03120 }
03121 AST_LIST_UNLOCK(&cfmtime_head);
03122
03123 return CLI_SUCCESS;
03124 }
03125
03126 static struct ast_cli_entry cli_config[] = {
03127 AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
03128 AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"),
03129 AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
03130 };
03131
03132 static void config_shutdown(void)
03133 {
03134 struct cache_file_mtime *cfmtime;
03135
03136 AST_LIST_LOCK(&cfmtime_head);
03137 while ((cfmtime = AST_LIST_REMOVE_HEAD(&cfmtime_head, list))) {
03138 config_cache_destroy_entry(cfmtime);
03139 }
03140 AST_LIST_UNLOCK(&cfmtime_head);
03141
03142 ast_cli_unregister_multiple(cli_config, ARRAY_LEN(cli_config));
03143 }
03144
03145 int register_config_cli(void)
03146 {
03147 ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
03148 ast_register_atexit(config_shutdown);
03149 return 0;
03150 }