Main Page | Alphabetical List | Data Structures | File List | Data Fields | Globals

config.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- A telephony toolkit for Linux.
00003  *
00004  * Configuration File Parser
00005  * 
00006  * Copyright (C) 1999, Mark Spencer
00007  *
00008  * Mark Spencer <markster@linux-support.net>
00009  *
00010  * This program is free software, distributed under the terms of
00011  * the GNU General Public License
00012  */
00013 
00014 #include <stdio.h>
00015 #include <unistd.h>
00016 #include <stdlib.h>
00017 #include <string.h>
00018 #include <errno.h>
00019 #include <time.h>
00020 #include <asterisk/config.h>
00021 #include <asterisk/options.h>
00022 #include <asterisk/logger.h>
00023 #include "asterisk.h"
00024 #include "astconf.h"
00025 
00026 #define MAX_INCLUDE_LEVEL 10
00027 
00028 struct ast_category {
00029    char name[80];
00030    struct ast_variable *root;
00031    struct ast_category *next;
00032 #ifdef PRESERVE_COMMENTS
00033    struct ast_comment *precomments;
00034    struct ast_comment *sameline;
00035 #endif   
00036 };
00037 
00038 struct ast_config {
00039    /* Maybe this structure isn't necessary but we'll keep it
00040       for now */
00041    struct ast_category *root;
00042    struct ast_category *prev;
00043 #ifdef PRESERVE_COMMENTS
00044    struct ast_comment *trailingcomments;
00045 #endif   
00046 };
00047 
00048 #ifdef PRESERVE_COMMENTS
00049 struct ast_comment_struct
00050 {
00051    struct ast_comment *root;
00052    struct ast_comment *prev;
00053 };
00054 #endif
00055 
00056 static char *strip(char *buf)
00057 {
00058    char *start;
00059    /* Strip off trailing whitespace, returns, etc */
00060    while(strlen(buf) && (buf[strlen(buf)-1]<33))
00061       buf[strlen(buf)-1] = '\0';
00062    start = buf;
00063    /* Strip off leading whitespace, returns, etc */
00064    while(*start && (*start < 33))
00065       *start++ = '\0';
00066    return start;
00067 }
00068 
00069 #ifdef PRESERVE_COMMENTS
00070 static void free_comments(struct ast_comment *com)
00071 {
00072    struct ast_comment *l;
00073    while (com) {
00074       l = com;
00075       com = com->next;
00076       free(l);
00077    }
00078 }
00079 #endif
00080 
00081 void ast_destroy(struct ast_config *ast)
00082 {
00083    struct ast_category *cat, *catn;
00084    struct ast_variable *v, *vn;
00085 
00086    if (!ast)
00087       return;
00088 
00089    cat = ast->root;
00090    while(cat) {
00091       v = cat->root;
00092       while(v) {
00093          vn = v;
00094          free(v->name);
00095          free(v->value);
00096 #ifdef PRESERVE_COMMENTS
00097          free_comments(v->precomments);
00098          free_comments(v->sameline);
00099 #endif         
00100          v = v->next;
00101          free(vn);
00102       }
00103       catn = cat;
00104 #ifdef PRESERVE_COMMENTS
00105       free_comments(cat->precomments);
00106       free_comments(cat->sameline);
00107 #endif      
00108       cat = cat->next;
00109       free(catn);
00110    }
00111 #ifdef PRESERVE_COMMENTS
00112    free_comments(ast->trailingcomments);
00113 #endif   
00114    free(ast);
00115 }
00116 
00117 int ast_true(char *s)
00118 {
00119    if (!s)
00120       return 0;
00121    /* Determine if this is a true value */
00122    if (!strcasecmp(s, "yes") ||
00123        !strcasecmp(s, "true") ||
00124       !strcasecmp(s, "y") ||
00125       !strcasecmp(s, "t") ||
00126       !strcasecmp(s, "1"))
00127          return -1;
00128    return 0;
00129 }
00130 
00131 struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
00132 {
00133    struct ast_category *cat;
00134    cat = config->root;
00135    while(cat) {
00136       if (cat->name == category)
00137          return cat->root;
00138       cat = cat->next;
00139    }
00140    cat = config->root;
00141    while(cat) {
00142       if (!strcasecmp(cat->name, category))
00143          return cat->root;
00144       cat = cat->next;
00145    }
00146    return NULL;
00147 }
00148 
00149 char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
00150 {
00151    struct ast_variable *v;
00152    if (category) {
00153       v = ast_variable_browse(config, category);
00154       while (v) {
00155          if (value == v->name)
00156             return v->value;
00157          v=v->next;
00158       }
00159       v = ast_variable_browse(config, category);
00160       while (v) {
00161          if (!strcasecmp(value, v->name))
00162             return v->value;
00163          v=v->next;
00164       }
00165    } else {
00166       struct ast_category *cat;
00167       cat = config->root;
00168       while(cat) {
00169          v = cat->root;
00170          while (v) {
00171             if (!strcasecmp(value, v->name))
00172                return v->value;
00173             v=v->next;
00174          }
00175          cat = cat->next;
00176       }
00177    }
00178    return NULL;
00179 }
00180 
00181 #ifdef PRESERVE_COMMENTS
00182 int ast_variable_delete(struct ast_config *cfg, char *category, char *variable, char *value)
00183 {
00184    struct ast_variable *v, *pv, *bv, *bpv;
00185    struct ast_category *cat;
00186    cat = cfg->root;
00187    while(cat) {
00188       if (cat->name == category) {
00189          break;
00190       }
00191       cat = cat->next;
00192    }
00193    if (!cat) {
00194       cat = cfg->root;
00195       while(cat) {
00196          if (!strcasecmp(cat->name, category)) {
00197             break;
00198          }
00199          cat = cat->next;
00200       }
00201    }
00202    if (!cat)
00203       return -1;
00204    v = cat->root;
00205    pv = NULL;
00206    while (v) {
00207       if ((variable == v->name) && (!value || !strcmp(v->value, value)))
00208          break;
00209       pv = v;
00210       v=v->next;
00211    }
00212    if (!v) {
00213       /* Get the last one that looks like it */
00214       bv = NULL;
00215       bpv = NULL;
00216       v = cat->root;
00217       pv = NULL;
00218       while (v) {
00219          if (!strcasecmp(variable, v->name) && (!value || !strcmp(v->value, value))) {
00220             bv = v;
00221             bpv = pv;
00222          }
00223          pv = v;
00224          v=v->next;
00225       }
00226       v = bv;
00227    }
00228 
00229    if (v) {
00230       /* Unlink from original position */
00231       if (pv) 
00232          pv->next = v->next;
00233       else
00234          cat->root = v->next;
00235       v->next = NULL;
00236       free(v->name);
00237       if (v->value)
00238          free(v->value);
00239       free_comments(v->sameline);
00240       free_comments(v->precomments);
00241       return 0;
00242    }
00243    return -1;
00244 }
00245 
00246 int ast_category_delete(struct ast_config *cfg, char *category)
00247 {
00248    struct ast_variable *v, *pv;
00249    struct ast_category *cat, *cprev;
00250    cat = cfg->root;
00251    cprev = NULL;
00252    while(cat) {
00253       if (cat->name == category) {
00254          break;
00255       }
00256       cprev = cat;
00257       cat = cat->next;
00258    }
00259    if (!cat) {
00260       cat = cfg->root;
00261       cprev = NULL;
00262       while(cat) {
00263          if (!strcasecmp(cat->name, category)) {
00264             break;
00265          }
00266          cprev = cat;
00267          cat = cat->next;
00268       }
00269    }
00270    if (!cat)
00271       return -1;
00272    /* Unlink it */
00273    if (cprev)
00274       cprev->next = cat->next;
00275    else
00276       cfg->root = cat->next;
00277    v = cat->root;
00278    while (v) {
00279       pv = v;
00280       v=v->next;
00281       if (pv->value)
00282          free(pv->value);
00283       if (pv->name)
00284          free(pv->name);
00285       free_comments(pv->sameline);
00286       free_comments(pv->precomments);
00287       free(pv);
00288    }
00289    free_comments(cat->sameline);
00290    free_comments(cat->precomments);
00291    free(cat);
00292    return 0;
00293 }
00294 
00295 struct ast_variable *ast_variable_append_modify(struct ast_config *config, char *category, char *variable, char *value, int newcat, int newvar, int move)
00296 {
00297    struct ast_variable *v, *pv=NULL, *bv, *bpv;
00298    struct ast_category *cat, *pcat;
00299    cat = config->root;
00300    if (!newcat) {
00301       while(cat) {
00302          if (cat->name == category) {
00303             break;
00304          }
00305          cat = cat->next;
00306       }
00307       if (!cat) {
00308          cat = config->root;
00309          while(cat) {
00310             if (!strcasecmp(cat->name, category)) {
00311                break;
00312             }
00313             cat = cat->next;
00314          }
00315       }
00316    }
00317    if (!cat) {
00318       cat = malloc(sizeof(struct ast_category));
00319       if (!cat)
00320          return NULL;
00321       memset(cat, 0, sizeof(struct ast_category));
00322       strncpy(cat->name, category, sizeof(cat->name));
00323       if (config->root) {
00324          /* Put us at the end */
00325          pcat = config->root;
00326          while(pcat->next)
00327             pcat = pcat->next;
00328          pcat->next = cat;
00329       } else {
00330          /* We're the first one */
00331          config->root = cat;
00332       }
00333          
00334    }
00335    if (!newvar) {
00336       v = cat->root;
00337       pv = NULL;
00338       while (v) {
00339          if (variable == v->name)
00340             break;
00341          pv = v;
00342          v=v->next;
00343       }
00344       if (!v) {
00345          /* Get the last one that looks like it */
00346          bv = NULL;
00347          bpv = NULL;
00348          v = cat->root;
00349          pv = NULL;
00350          while (v) {
00351             if (!strcasecmp(variable, v->name)) {
00352                bv = v;
00353                bpv = pv;
00354             }
00355             pv = v;
00356             v=v->next;
00357          }
00358          v = bv;
00359       }
00360    } else v = NULL;
00361    if (v && move) {
00362       /* Unlink from original position */
00363       if (pv) 
00364          pv->next = v->next;
00365       else
00366          cat->root = v->next;
00367       v->next = NULL;
00368    }
00369    if (!v) {
00370       v = malloc(sizeof(struct ast_variable));
00371       if (!v)
00372          return NULL;
00373       memset(v, 0, sizeof(struct ast_variable));
00374       v->name = strdup(variable);
00375       move = 1;
00376    }
00377    if (v->value)
00378       free(v->value);
00379    if (value)
00380       v->value = strdup(value);
00381    else
00382       v->value = strdup("");
00383    if (move) {
00384       if (cat->root) {
00385          pv = cat->root;
00386          while (pv->next) 
00387             pv = pv->next;
00388          pv->next = v;
00389       } else {
00390          cat->root = v;
00391       }
00392    }
00393    return v;
00394 }
00395 #endif      
00396 
00397 int ast_category_exist(struct ast_config *config, char *category_name)
00398 {
00399    struct ast_category *category = NULL;
00400 
00401    category = config->root;
00402 
00403    while(category) {
00404       if (!strcasecmp(category->name,category_name)) 
00405          return 1;
00406       category = category->next;
00407    } 
00408 
00409    return 0;
00410 }
00411 
00412 #ifdef PRESERVE_COMMENTS
00413 static struct ast_comment *build_comment(char *cmt)
00414 {
00415    struct ast_comment *c;
00416    int len = strlen(cmt) + 1;
00417    c = malloc(sizeof(struct ast_comment) + len);
00418    if (c) {
00419       /* Memset the header */
00420       memset(c, 0, sizeof(struct ast_comment));
00421       /* Copy the rest */
00422       strcpy(c->cmt, cmt);
00423    }
00424    return c;
00425 }
00426 #endif
00427 
00428 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
00429 #ifdef PRESERVE_COMMENTS
00430 , struct ast_comment_struct *acs
00431 #endif
00432 );
00433 
00434 static int cfg_process(struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, char *buf, int lineno, char *configfile, int includelevel 
00435 #ifdef PRESERVE_COMMENTS
00436 ,struct ast_comment_struct *acs
00437 #endif
00438 )
00439 {
00440    char *c;
00441    char *cur;
00442    struct ast_variable *v;
00443 #ifdef PRESERVE_COMMENTS
00444    struct ast_comment *com = NULL;
00445 #endif   
00446    int object;
00447    /* Strip off lines using ; as comment */
00448    c = strchr(buf, ';');
00449    if (c) {
00450       *c = '\0';
00451 #ifdef PRESERVE_COMMENTS
00452       c++;
00453       if (*c != '!')
00454          com = build_comment(c);
00455 #endif         
00456    }
00457    cur = strip(buf);
00458    if (strlen(cur)) {
00459       /* Actually parse the entry */
00460       if (cur[0] == '[') {
00461          /* A category header */
00462          c = strchr(cur, ']');
00463          if (c) {
00464             *c = 0;
00465             *_tmpc = malloc(sizeof(struct ast_category));
00466             if (!*_tmpc) {
00467                ast_destroy(tmp);
00468                ast_log(LOG_WARNING,
00469                   "Out of memory, line %d\n", lineno);
00470                return -1;
00471             }
00472             memset(*_tmpc, 0, sizeof(struct ast_category));
00473             strncpy((*_tmpc)->name, cur+1, sizeof((*_tmpc)->name) - 1);
00474             (*_tmpc)->root =  NULL;
00475 #ifdef PRESERVE_COMMENTS
00476             (*_tmpc)->precomments = acs->root;
00477             (*_tmpc)->sameline = com;
00478 #endif            
00479             if (!tmp->prev)
00480                tmp->root = *_tmpc;
00481             else
00482                tmp->prev->next = *_tmpc;
00483 
00484             tmp->prev = *_tmpc;
00485 #ifdef PRESERVE_COMMENTS
00486             acs->root = NULL;
00487             acs->prev = NULL;
00488 #endif            
00489             *_last =  NULL;
00490          } else {
00491             ast_log(LOG_WARNING, 
00492                "parse error: no closing ']', line %d of %s\n", lineno, configfile);
00493          }
00494       } else if (cur[0] == '#') {
00495          /* A directive */
00496          cur++;
00497          c = cur;
00498          while(*c && (*c > 32)) c++;
00499          if (*c) {
00500             *c = '\0';
00501             c++;
00502             /* Find real argument */
00503             while(*c  && (*c < 33)) c++;
00504             if (!*c)
00505                c = NULL;
00506          } else 
00507             c = NULL;
00508          if (!strcasecmp(cur, "include")) {
00509             /* A #include */
00510             if (c) {
00511                while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
00512                /* Get rid of leading mess */
00513                cur = c;
00514                while(strlen(cur)) {
00515                   c = cur + strlen(cur) - 1;
00516                   if ((*c == '>') || (*c == '<') || (*c == '\"'))
00517                      *c = '\0';
00518                   else
00519                      break;
00520                }
00521                if (includelevel < MAX_INCLUDE_LEVEL) {
00522                   __ast_load(cur, tmp, _tmpc, _last, includelevel + 1
00523 #ifdef PRESERVE_COMMENTS
00524                   ,acs
00525 #endif
00526                   );
00527                } else 
00528                   ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", includelevel);
00529             } else
00530                ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
00531             /* Strip off leading and trailing "'s and <>'s */
00532          } else 
00533             ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
00534       } else {
00535          /* Just a line (variable = value) */
00536          if (!*_tmpc) {
00537             ast_log(LOG_WARNING,
00538                "parse error: No category context for line %d of %s\n", lineno, configfile);
00539             ast_destroy(tmp);
00540             return -1;
00541          }
00542          c = strchr(cur, '=');
00543          if (c) {
00544             *c = 0;
00545             c++;
00546             /* Ignore > in => */
00547             if (*c== '>') {
00548                object = 1;
00549                c++;
00550             } else
00551                object = 0;
00552             v = malloc(sizeof(struct ast_variable));
00553             if (v) {
00554                memset(v, 0, sizeof(struct ast_variable));
00555                v->next = NULL;
00556                v->name = strdup(strip(cur));
00557                v->value = strdup(strip(c));
00558                v->lineno = lineno;
00559                v->object = object;
00560                /* Put and reset comments */
00561 #ifdef PRESERVE_COMMENTS
00562                v->precomments = acs->root;
00563                v->sameline = com;
00564                acs->prev = NULL;
00565                acs->root = NULL;
00566 #endif               
00567                v->blanklines = 0;
00568                if (*_last)
00569                   (*_last)->next = v;
00570                else
00571                   (*_tmpc)->root = v;
00572                *_last = v;
00573             } else {
00574                ast_destroy(tmp);
00575                ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
00576                return -1;
00577             }
00578          } else {
00579             ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
00580          }
00581                                           
00582       }
00583    } else {
00584       /* store any comments if there are any */
00585 #ifdef PRESERVE_COMMENTS
00586       if (com) {
00587          if (acs->prev)
00588             acs->prev->next = com;
00589          else
00590             acs->root = com;
00591          acs->prev = com;
00592       } else {
00593       if (*_last) 
00594          (*_last)->blanklines++;
00595 
00596       }
00597 #endif
00598    }
00599    return 0;
00600 }
00601 
00602 #ifdef PRESERVE_COMMENTS
00603 static void dump_comments(FILE *f, struct ast_comment *comment)
00604 {
00605    while (comment) {
00606       fprintf(f, ";%s", comment->cmt);
00607       comment = comment->next;
00608    }
00609 }
00610 #endif
00611 
00612 int ast_save(char *configfile, struct ast_config *cfg, char *generator)
00613 {
00614    FILE *f;
00615    char fn[256];
00616    char date[256];
00617    time_t t;
00618    struct ast_variable *var;
00619    struct ast_category *cat;
00620    int blanklines = 0;
00621    if (configfile[0] == '/') {
00622       strncpy(fn, configfile, sizeof(fn)-1);
00623    } else {
00624       snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
00625    }
00626    time(&t);
00627    strncpy(date, ctime(&t), sizeof(date));
00628    if ((f = fopen(fn, "w"))) {
00629       if ((option_verbose > 1) && !option_debug)
00630          ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
00631       fprintf(f, ";!\n");
00632       fprintf(f, ";! Automatically generated configuration file\n");
00633       fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
00634       fprintf(f, ";! Generator: %s\n", generator);
00635       fprintf(f, ";! Creation Date: %s", date);
00636       fprintf(f, ";!\n");
00637       cat = cfg->root;
00638       while(cat) {
00639 #ifdef PRESERVE_COMMENTS
00640          /* Dump any precomments */
00641          dump_comments(f, cat->precomments);
00642 #endif
00643          /* Dump section with any appropriate comment */
00644 #ifdef PRESERVE_COMMENTS
00645          if (cat->sameline) 
00646             fprintf(f, "[%s]  ; %s\n", cat->name, cat->sameline->cmt);
00647          else
00648 #endif
00649             fprintf(f, "[%s]\n", cat->name);
00650          var = cat->root;
00651          while(var) {
00652 #ifdef PRESERVE_COMMENTS
00653             dump_comments(f, var->precomments);
00654 #endif            
00655             if (var->sameline) 
00656                fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
00657             else  
00658                fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
00659             if (var->blanklines) {
00660                blanklines = var->blanklines;
00661                while (blanklines) {
00662                   fprintf(f, "\n");
00663                   blanklines--;
00664                }
00665             }
00666                
00667             var = var->next;
00668          }
00669 #if 0
00670          /* Put an empty line */
00671          fprintf(f, "\n");
00672 #endif
00673          cat = cat->next;
00674       }
00675 #ifdef PRESERVE_COMMENTS
00676       dump_comments(f, cfg->trailingcomments);
00677 #endif      
00678    } else {
00679       if (option_debug)
00680          printf("Unable to open for writing: %s\n", fn);
00681       else if (option_verbose > 1)
00682          printf( "Unable to write (%s)", strerror(errno));
00683       return -1;
00684    }
00685    fclose(f);
00686    return 0;
00687 }
00688 
00689 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
00690 #ifdef PRESERVE_COMMENTS
00691 , struct ast_comment_struct *acs
00692 #endif
00693 )
00694 {
00695    char fn[256];
00696    char buf[512];
00697    FILE *f;
00698    int lineno=0;
00699    int master=0;
00700 
00701    if (configfile[0] == '/') {
00702       strncpy(fn, configfile, sizeof(fn)-1);
00703    } else {
00704       snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, configfile);
00705    }
00706    if ((option_verbose > 1) && !option_debug) {
00707       ast_verbose(  VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
00708       fflush(stdout);
00709    }
00710    if ((f = fopen(fn, "r"))) {
00711       if (option_debug)
00712          ast_log(LOG_DEBUG, "Parsing %s\n", fn);
00713       else if (option_verbose > 1)
00714          ast_verbose( "Found\n");
00715       if (!tmp) {
00716          tmp = malloc(sizeof(struct ast_config));
00717          if (tmp)
00718             memset(tmp, 0, sizeof(struct ast_config));
00719 
00720          master = 1;
00721       }
00722       if (!tmp) {
00723          ast_log(LOG_WARNING, "Out of memory\n");
00724          fclose(f);
00725          return NULL;
00726       }
00727       while(!feof(f)) {
00728          fgets(buf, sizeof(buf), f);
00729          lineno++;
00730          if (!feof(f)) {
00731             if (cfg_process(tmp, _tmpc, _last, buf, lineno, configfile, includelevel
00732 #ifdef PRESERVE_COMMENTS
00733             , acs
00734 #endif
00735             )) {
00736                fclose(f);
00737                return NULL;
00738             }
00739          }
00740       }
00741       fclose(f);     
00742    } else {
00743       if (option_debug)
00744          ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
00745       else if (option_verbose > 1)
00746          ast_verbose( "Not found (%s)\n", strerror(errno));
00747    }
00748 #ifdef PRESERVE_COMMENTS
00749    if (master) {
00750       /* Keep trailing comments */
00751       tmp->trailingcomments = acs->root;
00752       acs->root = NULL;
00753       acs->prev = NULL;
00754    }
00755 #endif
00756    return tmp;
00757 }
00758 
00759 struct ast_config *ast_load(char *configfile)
00760 {
00761    struct ast_category *tmpc=NULL;
00762    struct ast_variable *last = NULL;
00763 #ifdef PRESERVE_COMMENTS
00764    struct ast_comment_struct acs = { NULL, NULL };
00765 #endif   
00766    return __ast_load(configfile, NULL, &tmpc, &last, 0 
00767 #ifdef PRESERVE_COMMENTS
00768    ,&acs
00769 #endif
00770    );
00771 }
00772 
00773 char *ast_category_browse(struct ast_config *config, char *prev)
00774 {  
00775    struct ast_category *cat;
00776    if (!prev) {
00777       if (config->root)
00778          return config->root->name;
00779       else
00780          return NULL;
00781    }
00782    cat = config->root;
00783    while(cat) {
00784       if (cat->name == prev) {
00785          if (cat->next)
00786             return cat->next->name;
00787          else
00788             return NULL;
00789       }
00790       cat = cat->next;
00791    }
00792    cat = config->root;
00793    while(cat) {
00794       if (!strcasecmp(cat->name, prev)) {
00795          if (cat->next)
00796             return cat->next->name;
00797          else
00798             return NULL;
00799       }
00800       cat = cat->next;
00801    }
00802    return NULL;
00803 }

Generated on Fri Oct 31 07:05:05 2003 for Asterisk by doxygen 1.3.4