Back to home page

Enduro/X

 
 

    


0001 /**
0002  * @brief Enduro/X advanced configuration driver
0003  *   We have a problem with logging here - chicken-egg issue.
0004  *   We cannot start to log unless debug is initialised. But debug init depends
0005  *   on ini config. Thus we print here debugs to stderr, if needed.
0006  *
0007  * @file inicfg.c
0008  */
0009 /* -----------------------------------------------------------------------------
0010  * Enduro/X Middleware Platform for Distributed Transaction Processing
0011  * Copyright (C) 2009-2016, ATR Baltic, Ltd. All Rights Reserved.
0012  * Copyright (C) 2017-2023, Mavimax, Ltd. All Rights Reserved.
0013  * This software is released under one of the following licenses:
0014  * AGPL (with Java and Go exceptions) or Mavimax's license for commercial use.
0015  * See LICENSE file for full text.
0016  * -----------------------------------------------------------------------------
0017  * AGPL license:
0018  *
0019  * This program is free software; you can redistribute it and/or modify it under
0020  * the terms of the GNU Affero General Public License, version 3 as published
0021  * by the Free Software Foundation;
0022  *
0023  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0024  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0025  * PARTICULAR PURPOSE. See the GNU Affero General Public License, version 3
0026  * for more details.
0027  *
0028  * You should have received a copy of the GNU Affero General Public License along 
0029  * with this program; if not, write to the Free Software Foundation, Inc.,
0030  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0031  *
0032  * -----------------------------------------------------------------------------
0033  * A commercial use license is available from Mavimax, Ltd
0034  * contact@mavimax.com
0035  * -----------------------------------------------------------------------------
0036  */
0037 
0038 /*---------------------------Includes-----------------------------------*/
0039 #include <ndrstandard.h>
0040 #include <time.h>
0041 #include <sys/time.h>
0042 #include <limits.h>
0043 #include <stdlib.h>
0044 #include <string.h>
0045 #include <stdio.h>
0046 #include <utlist.h>
0047 #include <nstdutil.h>
0048 #include <ndrx_ini.h>
0049 #include <inicfg.h>
0050 #include <nerror.h>
0051 #include <sys_unix.h>
0052 #include <errno.h>
0053 #include <ndebug.h>
0054 #include <nstd_int.h>
0055 /*---------------------------Externs------------------------------------*/
0056 /*---------------------------Macros-------------------------------------*/
0057 
0058 #define API_ENTRY {_Nunset_error();}
0059 
0060 /* #define INICFG_ENABLE_DEBUG  */
0061 /*---------------------------Enums--------------------------------------*/
0062 /*---------------------------Typedefs-----------------------------------*/
0063 /*---------------------------Globals------------------------------------*/
0064 /*---------------------------Statics------------------------------------*/
0065 /*---------------------------Prototypes---------------------------------*/
0066 exprivate ndrx_inicfg_t * _ndrx_inicfg_new(int load_global_env);
0067 exprivate int _ndrx_inicfg_reload(ndrx_inicfg_t *cfg, char **section_start_with);
0068 exprivate void cfg_mark_not_loaded(ndrx_inicfg_t *cfg, char *resource);
0069 exprivate void cfg_remove_not_marked(ndrx_inicfg_t *cfg);
0070 exprivate ndrx_inicfg_section_t * cfg_section_new(ndrx_inicfg_section_t **sections_h, char *section);
0071 exprivate ndrx_inicfg_section_t * cfg_section_get(ndrx_inicfg_section_t **sections_h, char *section);
0072 exprivate int handler(void* cf_ptr, void *vsection_start_with, void *cfg_ptr, 
0073         const char* section, const char* name, const char* value);
0074 exprivate int _ndrx_inicfg_load_single_file(ndrx_inicfg_t *cfg, 
0075         char *resource, char *fullname, char **section_start_with);
0076 exprivate ndrx_inicfg_file_t* cfg_single_file_get(ndrx_inicfg_t *cfg, char *fullname);
0077 exprivate int _ndrx_inicfg_update_single_file(ndrx_inicfg_t *cfg, 
0078         char *resource, char *fullname, char **section_start_with);
0079 exprivate int _ndrx_inicfg_add(ndrx_inicfg_t *cfg, char *resource, char **section_start_with);
0080 exprivate int _ndrx_keyval_hash_add(ndrx_inicfg_section_keyval_t **h, 
0081             ndrx_inicfg_section_keyval_t *src);
0082 exprivate ndrx_inicfg_section_keyval_t * _ndrx_keyval_hash_get(
0083         ndrx_inicfg_section_keyval_t *h, char *key);
0084 exprivate void _ndrx_keyval_hash_free(ndrx_inicfg_section_keyval_t *h);
0085 exprivate int _ndrx_inicfg_resolve(ndrx_inicfg_t *cfg, char **resources, char *section, 
0086         ndrx_inicfg_section_keyval_t **out);
0087 exprivate int _ndrx_inicfg_iterate(ndrx_inicfg_t *cfg, 
0088         char **resources,
0089         char **section_start_with, 
0090         ndrx_inicfg_section_t **out);
0091 exprivate void _ndrx_inicfg_sections_free(ndrx_inicfg_section_t *sections);
0092 exprivate void _ndrx_inicfg_file_free(ndrx_inicfg_t *cfg, ndrx_inicfg_file_t *cfgfile);
0093 exprivate void _ndrx_inicfg_free(ndrx_inicfg_t *cfg);
0094 
0095 /**
0096  * Create new config handler
0097  * @return ptr to config handler or NULL
0098  */
0099 exprivate ndrx_inicfg_t * _ndrx_inicfg_new(int load_global_env)
0100 {
0101     ndrx_inicfg_t *ret = NULL;
0102     
0103     if (NULL==(ret = NDRX_CALLOC(1, sizeof(ndrx_inicfg_t))))
0104     {
0105         _Nset_error_fmt(NEMALLOC, "Failed to malloc ndrx_inicfg_t: %s", 
0106                 strerror(errno));
0107 #ifdef INICFG_ENABLE_DEBUG
0108         fprintf(stderr, "Failed to alloc: ndrx_inicfg_t!\n");
0109 #endif
0110         goto out;
0111     }
0112     
0113     ret->load_global_env = load_global_env;
0114     
0115     NDRX_LOG_EARLY(log_debug, "%s: load_global_env: %d", __func__, load_global_env);
0116     
0117 out:
0118 #ifdef INICFG_ENABLE_DEBUG
0119     fprintf(stderr, "ndrx_inicfg_new returns %p\n", ret);
0120 #endif
0121     return ret;
0122 }
0123 
0124 /**
0125  * Reload the config, use the globals to search for value...
0126  * Already API version.
0127  * @param cfg
0128  * @param section_start_with
0129  * @return 
0130  */
0131 exprivate int _ndrx_inicfg_reload(ndrx_inicfg_t *cfg, char **section_start_with)
0132 {
0133     int i;
0134     int ret = EXSUCCEED;
0135     char fn[] = "_ndrx_inicfg_reload";
0136     string_hash_t * r, *rt;
0137     
0138     /* safe iter over the list */
0139     EXHASH_ITER(hh, cfg->resource_hash, r, rt)
0140     {
0141 #ifdef INICFG_ENABLE_DEBUG
0142         fprintf(stderr, "%s: Reloading [%s]\n", fn, r->str);
0143 #endif
0144         if (EXSUCCEED!=_ndrx_inicfg_add(cfg, r->str, section_start_with))
0145         {
0146             EXFAIL_OUT(ret);
0147         }
0148     }
0149     
0150 out:
0151 
0152 #ifdef INICFG_ENABLE_DEBUG
0153     fprintf(stderr, "%s: returns %d\n", fn, ret);
0154 #endif  
0155     return ret;
0156 }
0157 
0158 /**
0159  * Iterate over the resource and mark as not checked.
0160  * @param cfg
0161  * @param resource
0162  * @return 
0163  */
0164 exprivate void cfg_mark_not_loaded(ndrx_inicfg_t *cfg, char *resource)
0165 {
0166     ndrx_inicfg_file_t *f, *ftmp;
0167     
0168     EXHASH_ITER(hh, cfg->cfgfile, f, ftmp)
0169     {
0170         if (0==strcmp(f->resource, resource))
0171         {
0172 #ifdef INICFG_ENABLE_DEBUG
0173             fprintf(stderr, "Unrefreshing resource [%s] file [%s]\n", 
0174                     f->resource, f->fullname);
0175 #endif
0176         
0177             f->refreshed = EXFALSE;
0178         }
0179     }
0180 }
0181 
0182 /**
0183  * Remove any config file which is not reloaded
0184  * @param cfg
0185  * @param resource
0186  */
0187 exprivate void cfg_remove_not_marked(ndrx_inicfg_t *cfg)
0188 {
0189     ndrx_inicfg_file_t *f, *ftmp;
0190     
0191     EXHASH_ITER(hh, cfg->cfgfile, f, ftmp)
0192     {
0193         if (!f->refreshed)
0194         {
0195 #ifdef INICFG_ENABLE_DEBUG
0196             fprintf(stderr, "Resource [%s]/file [%s] not refreshed - removing from mem\n", 
0197                     f->resource, f->fullname);
0198 #endif
0199             ndrx_inicfg_file_free(cfg, f);
0200         }
0201     }
0202 }
0203 
0204 /**
0205  * Get new qhash entry + add it to hash.
0206  * @param qname
0207  * @return 
0208  */
0209 exprivate ndrx_inicfg_section_t * cfg_section_new(ndrx_inicfg_section_t **sections_h, char *section)
0210 {
0211     ndrx_inicfg_section_t * ret = NDRX_CALLOC(1, sizeof(ndrx_inicfg_section_t));
0212             
0213     if (NULL==ret)
0214     {
0215         int err = errno;
0216         _Nset_error_fmt(NEMALLOC, "Failed to malloc ndrx_inicfg_section_t: %s", strerror(err));
0217         goto out;
0218     }
0219     
0220     if (NULL==(ret->section=strdup(section)))
0221     {
0222         int err = errno;
0223         _Nset_error_fmt(NEMALLOC, "Failed to malloc ndrx_inicfg_section_t: "
0224                 "(section) %s", strerror(err));
0225         ret=NULL;
0226         goto out;
0227     }
0228     
0229 #ifdef INICFG_ENABLE_DEBUG
0230     fprintf(stderr, "Adding new section [%s]\n", ret->section);
0231 #endif
0232 
0233     EXHASH_ADD_KEYPTR(hh, (*sections_h), ret->section, 
0234             strlen(ret->section), ret);
0235     
0236 out:
0237     return ret;
0238 }
0239 
0240 /**
0241  * Get QHASH record for particular q
0242  * @param qname
0243  * @return 
0244  */
0245 exprivate ndrx_inicfg_section_t * cfg_section_get(ndrx_inicfg_section_t **sections_h, char *section)
0246 {
0247     ndrx_inicfg_section_t * ret = NULL;
0248    
0249     EXHASH_FIND_STR( (*sections_h), section, ret);
0250     
0251     if (NULL==ret)
0252     {
0253         ret = cfg_section_new(sections_h, section);
0254     }
0255     
0256     return ret;
0257 }
0258 
0259 /**
0260  * Add stuff to file content hash
0261  * @param cf_ptr
0262  * @param section
0263  * @param name
0264  * @param value
0265  * @return 
0266  */
0267 exprivate int handler(void* cf_ptr, void *vsection_start_with, void *cfg_ptr, 
0268         const char* section, const char* name, const char* value)
0269 {
0270     int ret = 1;
0271     int value_len;
0272     ndrx_inicfg_file_t *cf = (ndrx_inicfg_file_t*)cf_ptr;
0273     char **section_start_with = (char **)vsection_start_with;
0274     int needed = EXTRUE;
0275     
0276     ndrx_inicfg_section_t *mem_section = NULL;
0277     ndrx_inicfg_section_keyval_t * mem_value = NULL;
0278     
0279     /* check do we need this section at all */
0280 #ifdef INICFG_ENABLE_DEBUG
0281     fprintf(stderr, "Handler got: resource [%s]/file [%s] section [%s]"
0282             " name [%s] value [%s] cf %p\n", 
0283             cf->resource, cf->fullname, section, name, value, cf);
0284 #endif
0285     
0286     if (NULL!=section_start_with)
0287     {
0288         needed = EXFALSE;
0289         if (NULL==section_start_with)
0290         {
0291             needed = EXTRUE;
0292         }
0293         else while (NULL!=*section_start_with)
0294         {
0295             int len = NDRX_MIN(strlen(*section_start_with), strlen(section));
0296             
0297             if (0 == strncmp(*section_start_with, section, len))
0298             {
0299                 needed = EXTRUE;
0300                 break;
0301             }
0302             section_start_with++;
0303         }
0304     }
0305     
0306     /* section not needed. */
0307     if (!needed)
0308     {
0309 #ifdef INICFG_ENABLE_DEBUG
0310         fprintf(stderr, "Section not needed - skipping\n");
0311 #endif
0312         goto out;
0313     }
0314     
0315     /* add/get section */
0316     mem_section = cfg_section_get(&(cf->sections), (char *)section);
0317     if (NULL==mem_section)
0318     {
0319         ret = 0;
0320         goto out;
0321     }
0322     
0323     if (NULL!=_ndrx_keyval_hash_get(mem_section->values, (char *)name))
0324     {
0325         /* do not allow duplicates... */
0326         ret=1;
0327         goto out;
0328     }
0329             
0330     mem_value = NDRX_CALLOC(1, sizeof(ndrx_inicfg_section_keyval_t));
0331     
0332     if (NULL==mem_value)
0333     {
0334         int err = errno;
0335         _Nset_error_fmt(NEMALLOC, "Failed to malloc ndrx_inicfg_section_t: %s", strerror(err));
0336         ret = 0;
0337         goto out;
0338     }
0339     
0340     if (NULL==(mem_value->section = NDRX_STRDUP(section)))
0341     {
0342         int err = errno;
0343         _Nset_error_fmt(NEMALLOC, "Failed to malloc mem_value->section: %s", strerror(err));
0344         ret = 0;
0345         goto out;
0346     }
0347     
0348     /* Process the key */
0349     if (NULL==(mem_value->key = NDRX_STRDUP(name)))
0350     {
0351         int err = errno;
0352         _Nset_error_fmt(NEMALLOC, "Failed to malloc mem_value->key: %s", strerror(err));
0353         ret = 0;
0354         goto out;
0355     }
0356     
0357     if (NULL==(mem_value->val = NDRX_STRDUP(value)))
0358     {
0359         int err = errno;
0360         _Nset_error_fmt(NEMALLOC, "Failed to malloc mem_value->val: %s", strerror(err));
0361         ret = 0;
0362         goto out;
0363     }    
0364     
0365     value_len = strlen(mem_value->val) + PATH_MAX + 1;
0366     
0367     if (NULL==(mem_value->val = NDRX_REALLOC(mem_value->val, value_len)))
0368     {
0369         int err = errno;
0370         _Nset_error_fmt(NEMALLOC, "Failed to malloc mem_value->val (new size: %d): %s", 
0371                 value_len, strerror(err));
0372         ret = 0;
0373         goto out;
0374     }
0375     /* replace the env... */
0376     ndrx_str_env_subs_len(mem_value->val, value_len);
0377     value_len = strlen(mem_value->val) + 1;
0378     
0379     /* realloc to exact size */
0380     if (NULL==(mem_value->val = NDRX_REALLOC(mem_value->val, value_len)))
0381     {
0382         int err = errno;
0383         _Nset_error_fmt(NEMALLOC, "Failed to truncate mem_value->val to %d: %s", 
0384                 value_len, strerror(err));
0385 
0386         ret = 0;
0387         goto out;
0388     }
0389         
0390     /* Add stuff to the section 
0391      * TODO: what if we get the same key twice with different values?
0392      * wouldn't be mem-leak?
0393      * Thus lookup the hash before add
0394      */
0395     EXHASH_ADD_KEYPTR(hh, mem_section->values, mem_value->key, 
0396             /* should we include EOS? */
0397             strlen(mem_value->key)+1, mem_value);
0398     
0399 #ifdef INICFG_ENABLE_DEBUG
0400     fprintf(stderr, "section/key/value added OK\n");
0401 #endif
0402 
0403 out:
0404     return ret;
0405 }
0406 
0407 /**
0408  * Load single file into config
0409  * @param cfg
0410  * @param resource
0411  * @param section_start_with
0412  * @return 
0413  */
0414 exprivate int _ndrx_inicfg_load_single_file(ndrx_inicfg_t *cfg, 
0415         char *resource, char *fullname, char **section_start_with)
0416 {
0417     ndrx_inicfg_file_t *cf = NULL;
0418     int ret = EXSUCCEED;
0419     char fn[] = "_ndrx_inicfg_load_single_file";
0420     
0421     if (NULL==(cf = NDRX_CALLOC(1, sizeof(ndrx_inicfg_file_t))))
0422     {
0423         _Nset_error_fmt(NEMALLOC, "%s: Failed to malloc ndrx_inicfg_file_t: %s", 
0424                 fn, strerror(errno));
0425         EXFAIL_OUT(ret);
0426     }
0427     
0428     /* copy off resource */
0429     NDRX_STRCPY_SAFE(cf->resource, resource);
0430     /* copy off fullname of file */
0431     NDRX_STRCPY_SAFE(cf->fullname, fullname);
0432     
0433     cf->refreshed = EXTRUE;
0434     
0435     if (EXSUCCEED!=stat(fullname, &cf->attr))
0436     {
0437         _Nset_error_fmt(NEUNIX, "%s: stat() failed for [%s]:%s", 
0438                 fn, fullname, strerror(errno));
0439         EXFAIL_OUT(ret);    
0440     }
0441     
0442 #ifdef INICFG_ENABLE_DEBUG
0443     fprintf(stderr, "Opened config file %p\n", cf);
0444 #endif
0445 
0446     /* start to parse the file by inih */
0447     if (EXSUCCEED!=(ret=ndrx_ini_parse(fullname, handler, (void *)cf, 
0448             (void *)section_start_with, (void *)cfg)))
0449     {
0450         _Nset_error_fmt(NEINVALINI, "%s: Invalid ini file: [%s] error on line: %d", 
0451                 fn, fullname, ret);
0452         EXFAIL_OUT(ret);
0453     }
0454     
0455     EXHASH_ADD_STR( cfg->cfgfile, fullname, cf );
0456     
0457 out:
0458     return ret;
0459 }
0460 
0461 /**
0462  * Get the single file
0463  * @param cfg
0464  * @param fullname
0465  * @return 
0466  */
0467 exprivate ndrx_inicfg_file_t* cfg_single_file_get(ndrx_inicfg_t *cfg, char *fullname)
0468 {
0469     ndrx_inicfg_file_t * ret = NULL;
0470    
0471     EXHASH_FIND_STR( cfg->cfgfile, fullname, ret);
0472     
0473     return ret;
0474 }
0475 
0476 
0477 /**
0478  * Check the file for changes, if found in hash & 
0479  * @param cfg
0480  * @param resource
0481  * @param fullname
0482  * @param section_start_with
0483  * @return 
0484  */
0485 exprivate int _ndrx_inicfg_update_single_file(ndrx_inicfg_t *cfg, 
0486         char *resource, char *fullname, char **section_start_with)
0487 {
0488     int ret = EXSUCCEED;
0489     struct stat attr; 
0490     char fn[] = "_ndrx_inicfg_update_single_file";
0491     int ferr = 0;
0492     /* try to get the file handler (resolve) */
0493     ndrx_inicfg_file_t *cf = cfg_single_file_get(cfg, fullname);
0494     
0495 #ifdef INICFG_ENABLE_DEBUG
0496     fprintf(stderr, "%s: enter resource [%s]/file [%s]\n",
0497                     fn, resource, fullname);
0498 #endif
0499 
0500     if (EXSUCCEED!=stat(fullname, &attr))
0501     {
0502         /* check the error. */
0503         ferr = errno;
0504     }
0505         
0506 #ifdef INICFG_ENABLE_DEBUG
0507     if (NULL!=cf)
0508     {
0509         fprintf(stderr, "%s: tstamp: cur: %ld vs prev: %ld\n",
0510                         fn, attr.st_mtime, cf->attr.st_mtime);
0511     }
0512 #endif
0513     
0514     if (NULL!=cf && EXSUCCEED==ferr && 
0515             0!=memcmp(&(attr.st_mtime), &(cf->attr.st_mtime), sizeof(attr.st_mtime)))
0516     {
0517 #ifdef INICFG_ENABLE_DEBUG
0518         fprintf(stderr, "%s: [%s]/file [%s] changed - reload\n",
0519                         fn, resource, fullname);
0520 #endif
0521         /* reload the file - kill the old one, and load again */
0522         ndrx_inicfg_file_free(cfg, cf);
0523         if (EXSUCCEED!=_ndrx_inicfg_load_single_file(cfg, resource, fullname, 
0524                 section_start_with))
0525         {
0526             EXFAIL_OUT(ret);
0527         }
0528     }
0529     else if (NULL!=cf && EXSUCCEED==ferr)
0530     {
0531         /* config not changed, mark as refreshed */
0532 #ifdef INICFG_ENABLE_DEBUG
0533         fprintf(stderr, "%s: [%s]/file [%s] not-changed - do nothing\n",
0534                         fn, resource, fullname);
0535 #endif
0536         cf->refreshed = EXTRUE;
0537         goto out;
0538     }
0539     else if (NULL==cf && EXSUCCEED==ferr)
0540     {
0541 #ifdef INICFG_ENABLE_DEBUG
0542         fprintf(stderr, "%s: [%s]/file [%s] config does not exists, "
0543                 "but file exists - load\n",
0544                         fn, resource, fullname);
0545 #endif
0546         /* config does not exists, but file exists - load */
0547         if (EXSUCCEED!=_ndrx_inicfg_load_single_file(cfg, resource, fullname, 
0548                 section_start_with))
0549         {
0550             EXFAIL_OUT(ret);
0551         }
0552     }
0553     else if (NULL!=cf && EXSUCCEED!=ferr)
0554     {
0555         /* Config exits, file not, kill the config  */
0556 #ifdef INICFG_ENABLE_DEBUG
0557         fprintf(stderr, "%s: [%s]/file [%s] Config exits, "
0558                 "file not, kill the config\n",
0559                         fn, resource, fullname);
0560 #endif
0561         ndrx_inicfg_file_free(cfg, cf);
0562     }
0563     
0564 out:
0565     return ret;    
0566 }
0567 
0568 /**
0569  * Load or update resource
0570  * @param cfg config handler
0571  * @param resource folder/file to load
0572  * @param section_start_with list of sections which we are interested in
0573  * @return 
0574  */
0575 exprivate int _ndrx_inicfg_add(ndrx_inicfg_t *cfg, char *resource, char **section_start_with)
0576 {
0577     int ret = EXSUCCEED;
0578     char fn[] = "_ndrx_inicfg_add";
0579     cfg_mark_not_loaded(cfg, resource);
0580  
0581     /* Check the type (folder or file) 
0582      * If it is not file, then it is a directory (assumption).
0583      */
0584     if (ndrx_file_regular(resource))
0585     {
0586 #ifdef INICFG_ENABLE_DEBUG
0587         fprintf(stderr, "Resource: [%s] is regular file\n", resource);
0588 #endif
0589         if (EXSUCCEED!=_ndrx_inicfg_update_single_file(cfg, resource, 
0590                 resource, section_start_with))
0591         {
0592             EXFAIL_OUT(ret);
0593         }
0594     }
0595     else
0596     {
0597         string_list_t* flist = NULL;
0598         string_list_t* elt = NULL;
0599 
0600         int return_status = EXSUCCEED;
0601         
0602 #ifdef INICFG_ENABLE_DEBUG  
0603         fprintf(stderr, "Resource: [%s] seems like directory "
0604                 "(checking for *.ini, *.cfg, *.conf, *.config)\n", resource);
0605 #endif
0606         
0607         if (NULL!=(flist=ndrx_sys_folder_list(resource, &return_status)))
0608         {
0609            LL_FOREACH(flist,elt)
0610            {
0611                int len = strlen(elt->qname);
0612                if (    (len >=4 && 0==strcmp(elt->qname+len-4, ".ini")) ||
0613                        (len >=4 && 0==strcmp(elt->qname+len-4, ".cfg")) ||
0614                        (len >=5 && 0==strcmp(elt->qname+len-5, ".conf")) ||
0615                        (len >=7 && 0==strcmp(elt->qname+len-7, ".config"))
0616                    )
0617                {
0618                    char tmp[PATH_MAX+1];
0619                    snprintf(tmp, sizeof(tmp), "%s/%s", resource, elt->qname);
0620                    
0621                    if (EXSUCCEED!=_ndrx_inicfg_update_single_file(cfg, resource, 
0622                            tmp, section_start_with))
0623                    {
0624                        EXFAIL_OUT(ret);
0625                    }
0626                }         
0627            }
0628         }
0629         
0630         ndrx_string_list_free(flist);
0631     }
0632     
0633     cfg_remove_not_marked(cfg);
0634     
0635     /*
0636      * Add resource to the hash
0637      */
0638     if (NULL==ndrx_string_hash_get(cfg->resource_hash, resource))
0639     {
0640 #ifdef INICFG_ENABLE_DEBUG  
0641         fprintf(stderr, "Registering resource [%s]\n", resource);
0642 #endif
0643         if (NULL==ndrx_string_hash_add(&(cfg->resource_hash), resource))
0644         {
0645             _Nset_error_fmt(NEMALLOC, "%s: ndrx_string_hash_add - malloc failed", fn);
0646             EXFAIL_OUT(ret);
0647         }
0648     }
0649     
0650 out:
0651     return ret;
0652 }
0653 
0654 /**
0655  * Add item to keyval hash
0656  * @param h
0657  * @param str
0658  * @return SUCCEED/FAIL
0659  */
0660 exprivate int _ndrx_keyval_hash_add(ndrx_inicfg_section_keyval_t **h, 
0661             ndrx_inicfg_section_keyval_t *src)
0662 {
0663     int ret = EXSUCCEED;
0664     char fn[]="_ndrx_keyval_hash_add";
0665     ndrx_inicfg_section_keyval_t * tmp = NDRX_CALLOC(1, sizeof(ndrx_inicfg_section_keyval_t));
0666     
0667     if (NULL==tmp)
0668     {
0669 #ifdef INICFG_ENABLE_DEBUG
0670         fprintf(stderr, "alloc of ndrx_inicfg_section_keyval_t (%d) failed\n", 
0671                 (int)sizeof(ndrx_inicfg_section_keyval_t));
0672 #endif
0673         EXFAIL_OUT(ret);
0674     }
0675     
0676     if (NULL==(tmp->key = strdup(src->key)))
0677     {
0678 #ifdef INICFG_ENABLE_DEBUG
0679         fprintf(stderr, "strdup() failed: %s\n", strerror(errno));
0680         _Nset_error_fmt(NEMALLOC, "%s: malloc failed", fn);
0681 #endif
0682         EXFAIL_OUT(ret);
0683     }
0684     
0685     if (NULL==(tmp->val = strdup(src->val)))
0686     {
0687 #ifdef INICFG_ENABLE_DEBUG
0688         fprintf(stderr, "strdup() failed: %s\n", strerror(errno));
0689         _Nset_error_fmt(NEMALLOC, "%s: malloc failed", fn);
0690         
0691 #endif
0692         EXFAIL_OUT(ret);
0693     }
0694     
0695     if (NULL==(tmp->section = strdup(src->section)))
0696     {
0697 #ifdef INICFG_ENABLE_DEBUG
0698         fprintf(stderr, "strdup() failed: %s\n", strerror(errno));
0699         _Nset_error_fmt(NEMALLOC, "%s: malloc failed", fn);
0700 #endif
0701         EXFAIL_OUT(ret);
0702     }
0703     
0704     /* Add stuff to hash finaly */
0705     EXHASH_ADD_KEYPTR( hh, (*h), tmp->key, strlen(tmp->key), tmp );
0706     
0707 out:
0708     return ret;
0709 }
0710 
0711 /**
0712  * Search for string existence in hash (not need for API version)
0713  * @param h hash handler
0714  * @param str keyval to search for
0715  * @return NULL not found/not NULL - found
0716  */
0717 exprivate ndrx_inicfg_section_keyval_t * _ndrx_keyval_hash_get(
0718         ndrx_inicfg_section_keyval_t *h, char *key)
0719 {
0720     ndrx_inicfg_section_keyval_t * r = NULL;
0721     
0722     EXHASH_FIND_STR( h, key, r);
0723     
0724     return r;
0725 }
0726 
0727 /**
0728  * Free up the hash list (no need for API)
0729  * @param h
0730  * @return 
0731  */
0732 exprivate void _ndrx_keyval_hash_free(ndrx_inicfg_section_keyval_t *h)
0733 {
0734     ndrx_inicfg_section_keyval_t * r=NULL, *rt=NULL;
0735     /* safe iter over the list */
0736     EXHASH_ITER(hh, h, r, rt)
0737     {
0738         EXHASH_DEL(h, r);
0739         NDRX_FREE(r->key);
0740         NDRX_FREE(r->val);
0741         NDRX_FREE(r->section);
0742         NDRX_FREE(r);
0743     }
0744 }
0745 
0746 /**
0747  * Resolve the section
0748  * @param cfg
0749  * @param section
0750  * @param out
0751  * @return 
0752  */
0753 exprivate int _ndrx_inicfg_resolve(ndrx_inicfg_t *cfg, char **resources, char *section, 
0754         ndrx_inicfg_section_keyval_t **out)
0755 {
0756     int i;
0757     int found;
0758     int ret = EXSUCCEED;
0759     char fn[] = "_ndrx_inicfg_resolve";
0760     /* Loop over all resources, and check that these are present in  
0761      * resources var (or resources is NULL) 
0762      * in that case resolve from all resources found in system.
0763      */
0764     
0765     /* HASH FOR EACH: cfg->cfgfile */
0766     
0767     /* check by ndrx_keyval_hash_get() for result 
0768      * if not found, add...
0769      * if found ignore.
0770      */
0771     ndrx_inicfg_file_t * config_file=NULL, *config_file_temp=NULL;
0772     ndrx_inicfg_section_t *section_hash;
0773     
0774 #ifdef INICFG_ENABLE_DEBUG
0775     fprintf(stderr, "%s: lookup section [%s]\n", fn, section);
0776 #endif
0777     
0778     /* Iter over all resources */
0779     EXHASH_ITER(hh, cfg->cfgfile, config_file, config_file_temp)
0780     {
0781         found = EXFALSE;
0782         i = 0;
0783         
0784 #ifdef INICFG_ENABLE_DEBUG
0785         fprintf(stderr, "%s: Checking [%s]...\n", fn, config_file->fullname);
0786 #endif
0787     
0788         /* If resources is NULL, then look in all config files stored.  */
0789         if (NULL==resources)
0790         {
0791             found = EXTRUE;
0792 #ifdef INICFG_ENABLE_DEBUG
0793             fprintf(stderr, "%s: Checking [%s] - accept any\n", fn, config_file->fullname);
0794 #endif
0795         }
0796         else 
0797         {
0798             while(NULL!=resources[i])
0799             {
0800                 if (0==strcmp(config_file->resource, resources[i]))
0801                 {
0802                     found = EXTRUE;
0803                     break;
0804                 }
0805                 i++;
0806             }
0807         }
0808     
0809         if (found)
0810         {
0811 #ifdef INICFG_ENABLE_DEBUG
0812             fprintf(stderr, "%s: Checking [%s] - accepted\n", fn, config_file->fullname);
0813 #endif
0814             /* find section */
0815 #ifdef INICFG_ENABLE_DEBUG
0816             fprintf(stderr, "%s: searching for section [%s] in %p\n", 
0817                 fn, section, config_file->sections);
0818 #endif
0819             EXHASH_FIND_STR(config_file->sections, section, section_hash);
0820             if (NULL!=section_hash)
0821             {
0822                 
0823 #ifdef INICFG_ENABLE_DEBUG
0824                 fprintf(stderr, "%s: got section...\n", fn);
0825 #endif
0826                 ndrx_inicfg_section_keyval_t *vals = NULL, *vals_tmp = NULL;
0827                 /* ok we got a section, now get the all values down in section */
0828                 EXHASH_ITER(hh, (section_hash->values), vals, vals_tmp)
0829                 {
0830                     ndrx_inicfg_section_keyval_t *existing = NULL;
0831 #ifdef INICFG_ENABLE_DEBUG
0832                     fprintf(stderr, "%s: got section[%s]/key[%s]/val[%s]\n", fn, 
0833                             vals->section, vals->key, vals->val);
0834 #endif
0835                     existing = _ndrx_keyval_hash_get((*out), vals->key); 
0836                     /* Allow deeper sections to override higher sections. */
0837                     if (NULL==existing || 
0838                             ndrx_nr_chars(vals->section, NDRX_INICFG_SUBSECT_SPERATOR) > 
0839                             ndrx_nr_chars(existing->section, NDRX_INICFG_SUBSECT_SPERATOR))
0840                     {
0841                         if (EXSUCCEED!=_ndrx_keyval_hash_add(out, vals))
0842                         {
0843                             EXFAIL_OUT(ret);
0844                         }
0845                     }
0846                 } /* it over the key-vals in section */
0847             } /* if section found */
0848         } /* if file found in lookup resources  */
0849         else
0850         {
0851 #ifdef INICFG_ENABLE_DEBUG
0852             fprintf(stderr, "%s: Checking [%s] - NOT accepted\n", fn, config_file->fullname);
0853 #endif
0854         }
0855     } /* iter over config files */
0856     
0857     
0858 out:
0859 #ifdef INICFG_ENABLE_DEBUG
0860         fprintf(stderr, "%s: returns %p\n", fn, *out);
0861 #endif
0862 
0863     return ret;
0864 }
0865 
0866 /**
0867  * Resolve values including sub-sections
0868  * [SOME/SECTION/AND/SUBSECT]
0869  * We need to resolve in this order:
0870  * 1. SOME/SECTION/AND/SUBSECT
0871  * 2. SOME/SECTION/AND
0872  * 3. SOME/SECTION
0873  * 4. SOME
0874  * @param cfg
0875  * @param section
0876  * @param subsect
0877  * @return 
0878  */
0879 expublic int ndrx_inicfg_get_subsect_int(ndrx_inicfg_t *cfg, 
0880         char **resources, char *section, ndrx_inicfg_section_keyval_t **out)
0881 {
0882     int ret = EXSUCCEED;
0883     char *tmp = NULL;
0884     char *p;
0885     
0886     if (NULL==cfg)
0887     {
0888         _Nset_error_fmt(NEINVAL, "%s: `cfg' cannot be NULL!", __func__);
0889         EXFAIL_OUT(ret);
0890     }
0891     
0892     if (NULL==section)
0893     {
0894         _Nset_error_fmt(NEINVAL, "%s: `section' cannot be NULL!", __func__);
0895         EXFAIL_OUT(ret);
0896     }
0897 
0898     tmp = strdup(section);
0899 
0900     if (NULL==tmp)
0901     {
0902         _Nset_error_fmt(NEMALLOC, "%s: malloc failed", __func__);
0903         EXFAIL_OUT(ret);
0904     }
0905     
0906     while (EXEOS!=tmp[0])
0907     {
0908         if (EXSUCCEED!=_ndrx_inicfg_resolve(cfg, resources, tmp, out))
0909         {
0910             EXFAIL_OUT(ret);
0911         }
0912         p = strrchr(tmp, NDRX_INICFG_SUBSECT_SPERATOR);
0913         
0914         if (NULL!=p)
0915         {
0916             *p = EXEOS;
0917         }
0918         else
0919         {
0920             break; /* terminate, we are done */
0921         }
0922     }
0923     
0924 out:
0925     if (NULL!=tmp)
0926     {
0927         NDRX_FREE(tmp);
0928     }
0929 
0930     return ret;
0931 }
0932 
0933 /**
0934  * Iterate over the sections & return the matched image
0935  * We might want to return multiple hashes here of the sections found.
0936  * TODO: Think about iteration... Get the list based on sections
0937  * @param cfg
0938  * @param fullsection_starts_with
0939  * @return List of sections
0940  */
0941 exprivate int _ndrx_inicfg_iterate(ndrx_inicfg_t *cfg, 
0942         char **resources,
0943         char **section_start_with, 
0944         ndrx_inicfg_section_t **out)
0945 {
0946     int i;
0947     int found;
0948     int ret = EXSUCCEED;
0949     char fn[] = "_ndrx_inicfg_iterate";
0950     /* Loop over all resources, and check that these are present in  
0951      * resources var (or resources is NULL) 
0952      * in that case resolve from all resources found in system.
0953      */
0954     
0955     /* HASH FOR EACH: cfg->cfgfile */
0956     
0957     /* check by ndrx_keyval_hash_get() for result 
0958      * if not found, add...
0959      * if found ignore.
0960      */
0961     ndrx_inicfg_file_t * config_file=NULL, *config_file_temp=NULL;
0962     ndrx_inicfg_section_t *section = NULL, *section_temp=NULL;
0963     ndrx_inicfg_section_t *section_work = NULL;
0964 
0965 #ifdef INICFG_ENABLE_DEBUG
0966     fprintf(stderr, "%s: enter\n", fn);
0967 #endif
0968 
0969     /* Iter over all resources */
0970     EXHASH_ITER(hh, cfg->cfgfile, config_file, config_file_temp)
0971     {
0972         found = EXFALSE;
0973         i = 0;
0974         
0975         /* If resources is NULL, then look in all config files stored.  */
0976         if (NULL==resources)
0977         {
0978             found = EXTRUE;
0979         }
0980         else 
0981         {
0982             while(NULL!=resources[i])
0983             {
0984                 if (0==strcmp(config_file->resource, resources[i]))
0985                 {
0986                     found = EXTRUE;
0987                     break;
0988                 }
0989                 i++;
0990             }
0991         }
0992         
0993 #ifdef INICFG_ENABLE_DEBUG
0994     fprintf(stderr, "%s: resource [%s] %s for lookup\n", fn, config_file->resource, 
0995             found?"ok":"not ok");
0996 #endif
0997 
0998         if (found)
0999         {
1000             /* find section 
1001              * - loop over the file sections & fill the results
1002              * - will do the lookup with population of parent info into
1003              * childs
1004             EXHASH_FIND_STR(config_file->sections, section, section_hash);
1005              * */
1006             EXHASH_ITER(hh, (config_file->sections), section, section_temp)
1007             {
1008                 int len;
1009                 
1010                 found = EXFALSE;
1011                 i = 0;
1012                 
1013                 if (NULL==section_start_with)
1014                 {
1015                     found = EXTRUE;
1016                 }
1017                 else while (NULL!=section_start_with[i])
1018                 {
1019                     len = NDRX_MIN(strlen(section->section), strlen(section_start_with[i]));
1020                     if (0==strncmp(section->section, section_start_with[i], len))
1021                     {
1022                         found = EXTRUE;
1023                         break;
1024                     }
1025                     i++;
1026                 }
1027 
1028 #ifdef INICFG_ENABLE_DEBUG
1029     fprintf(stderr, "%s: section [%s] %s for lookup\n", fn, section->section, 
1030             found?"ok":"not ok");
1031 #endif
1032                 if (found)
1033                 {
1034                     /* build up the result section hash (check is there on or missing)... */
1035                     if (NULL==(section_work=cfg_section_get(out, section->section)))
1036                     {
1037                         EXFAIL_OUT(ret);
1038                     }
1039 
1040                     ndrx_inicfg_section_keyval_t *vals = NULL, *vals_tmp = NULL;
1041                     /* ok we got a section, now get the all values down in section */
1042                     EXHASH_ITER(hh, (section->values), vals, vals_tmp)
1043                     {
1044                         if (NULL==_ndrx_keyval_hash_get((section_work->values), vals->key))
1045                         {
1046                             if (EXSUCCEED!=_ndrx_keyval_hash_add(&(section_work->values), vals))
1047                             {
1048                                 EXFAIL_OUT(ret);
1049                             }
1050                         }
1051                     } /* it over the key-vals in section */
1052                 }
1053             }
1054         } /* if file found in lookup resources  */
1055     } /* iter over config files */
1056     
1057     
1058 out:
1059                 
1060 #ifdef INICFG_ENABLE_DEBUG
1061     fprintf(stderr, "%s: returns %p", fn, *out);
1062 #endif
1063 
1064     return ret;
1065 }
1066 
1067 /**
1068  * Free all sections from hash
1069  * @param sections ptr becomes invalid after function call
1070  */
1071 exprivate void _ndrx_inicfg_sections_free(ndrx_inicfg_section_t *sections)
1072 {
1073     char fn[] = "_ndrx_inicfg_sections_free";    
1074     ndrx_inicfg_section_t *section=NULL, *section_temp=NULL;
1075 #ifdef INICFG_ENABLE_DEBUG
1076     fprintf(stderr, "%s: enter %p\n", fn, sections);
1077 #endif
1078         
1079     /* kill the sections */
1080     EXHASH_ITER(hh, sections, section, section_temp)
1081     {
1082         EXHASH_DEL(sections, section);
1083         ndrx_keyval_hash_free(section->values);
1084         NDRX_FREE(section->section);
1085         NDRX_FREE(section);
1086     }
1087 }
1088 
1089 /**
1090  * Free the memory of file
1091  * @param cfg
1092  * @param fullfile
1093  * @return 
1094  */
1095 exprivate void _ndrx_inicfg_file_free(ndrx_inicfg_t *cfg, ndrx_inicfg_file_t *cfgfile)
1096 {
1097     char fn[] = "_ndrx_inicfg_file_free";
1098     ndrx_inicfg_section_t *section=NULL, *section_temp=NULL;
1099     
1100 #ifdef INICFG_ENABLE_DEBUG
1101     fprintf(stderr, "%s: enter cfg = %p cfgfile = %p\n", fn, cfg, cfgfile);
1102 #endif
1103     
1104     EXHASH_DEL(cfg->cfgfile, cfgfile);
1105     
1106     ndrx_inicfg_sections_free(cfgfile->sections);
1107     
1108     NDRX_FREE(cfgfile);
1109 }
1110 
1111 /**
1112  * Free the whole config
1113  * @param cfg config handler will become invalid after this operation
1114  * @return 
1115  */
1116 exprivate void _ndrx_inicfg_free(ndrx_inicfg_t *cfg)
1117 {
1118     char fn[]="_ndrx_inicfg_free";
1119     ndrx_inicfg_file_t *cf=NULL, *cf_tmp=NULL;
1120     
1121 #ifdef INICFG_ENABLE_DEBUG
1122     fprintf(stderr, "%s: enter cfg = %p\n", fn, cfg);
1123 #endif
1124     
1125     EXHASH_ITER(hh, cfg->cfgfile, cf, cf_tmp)
1126     {
1127         _ndrx_inicfg_file_free(cfg, cf);
1128     }
1129     
1130     ndrx_string_hash_free(cfg->resource_hash);
1131     
1132     NDRX_FREE(cfg);
1133 }
1134 
1135 /* ===========================================================================*/
1136 /* =========================API FUNCTIONS=====================================*/
1137 /* ===========================================================================*/
1138 /**
1139  * Reload the config, use the globals to search for value...
1140  * Already API version.
1141  * @param cfg
1142  * @param section_start_with
1143  * @return 
1144  */
1145 expublic int ndrx_inicfg_reload(ndrx_inicfg_t *cfg, char **section_start_with)
1146 {
1147     API_ENTRY;
1148     return _ndrx_inicfg_reload(cfg, section_start_with);
1149 }
1150 
1151 /**
1152  * Create new config handler
1153  * @return ptr to config handler or NULL
1154  */
1155 expublic ndrx_inicfg_t * ndrx_inicfg_new(void)
1156 {
1157     API_ENTRY;
1158     return _ndrx_inicfg_new(EXFALSE);
1159 }
1160 
1161 /**
1162  * Parametrised version of config new
1163  * @return ptr to config handler or NULL
1164  */
1165 expublic ndrx_inicfg_t * ndrx_inicfg_new2(int load_global_env)
1166 {
1167     API_ENTRY;
1168     return _ndrx_inicfg_new(load_global_env);
1169 }
1170 
1171 /**
1172  * API version of _ndrx_inicfg_load_single_file
1173  */
1174 expublic int ndrx_inicfg_load_single_file(ndrx_inicfg_t *cfg, 
1175         char *resource, char *fullname, char **section_start_with)
1176 {
1177     API_ENTRY;
1178     
1179     return _ndrx_inicfg_load_single_file(cfg, resource, fullname, section_start_with);
1180     
1181 }
1182 
1183 /**
1184  * API version of _ndrx_inicfg_update_single_file
1185  */
1186 expublic int ndrx_inicfg_update_single_file(ndrx_inicfg_t *cfg, 
1187         char *resource, char *fullname, char **section_start_with)
1188 {
1189     API_ENTRY;
1190     return _ndrx_inicfg_update_single_file(cfg, resource, fullname, section_start_with);
1191 }
1192 
1193 
1194 /**
1195  * Load or update resource (api version of _ndrx_inicfg_add)
1196  * @param cfg config handler
1197  * @param resource folder/file to load
1198  * @param section_start_with list of sections which we are interested in
1199  * @return 
1200  */
1201 expublic int ndrx_inicfg_add(ndrx_inicfg_t *cfg, char *resource, char **section_start_with)
1202 {
1203     API_ENTRY;
1204     return _ndrx_inicfg_add(cfg, resource, section_start_with);
1205 }
1206 
1207 /**
1208  * Add item to keyval hash (api version of _ndrx_keyval_hash_add)
1209  * @param h
1210  * @param str
1211  * @return SUCCEED/FAIL
1212  */
1213 expublic int ndrx_keyval_hash_add(ndrx_inicfg_section_keyval_t **h, 
1214             ndrx_inicfg_section_keyval_t *src)
1215 {
1216     API_ENTRY;
1217     return ndrx_keyval_hash_add(h, src);
1218 }
1219 
1220 /**
1221  * API version of _ndrx_keyval_hash_get
1222  * @param h
1223  * @param key
1224  * @return 
1225  */
1226 expublic ndrx_inicfg_section_keyval_t * ndrx_keyval_hash_get(
1227         ndrx_inicfg_section_keyval_t *h, char *key)
1228 {
1229     API_ENTRY;
1230     return _ndrx_keyval_hash_get(h, key);
1231 }
1232 
1233 /**
1234  * Free up the hash list (no need for API)
1235  * @param h
1236  * @return 
1237  */
1238 expublic void ndrx_keyval_hash_free(ndrx_inicfg_section_keyval_t *h)
1239 {
1240     API_ENTRY;
1241     _ndrx_keyval_hash_free(h);
1242 }
1243 
1244 /**
1245  * Resolve the section (api version of _ndrx_inicfg_resolve)
1246  * @param cfg
1247  * @param section
1248  * @param out
1249  * @return 
1250  */
1251 expublic int ndrx_inicfg_resolve(ndrx_inicfg_t *cfg, char **resources, char *section, 
1252         ndrx_inicfg_section_keyval_t **out)
1253 {
1254     API_ENTRY;
1255     return _ndrx_inicfg_resolve(cfg, resources, section, out);
1256 }
1257 
1258 /**
1259  * Resolve values including sub-sections, API version of ndrx_inicfg_get_subsect
1260  * [SOME/SECTION/AND/SUBSECT]
1261  * We need to resolve in this order:
1262  * 1. SOME/SECTION/AND/SUBSECT
1263  * 2. SOME/SECTION/AND
1264  * 3. SOME/SECTION
1265  * 4. SOME
1266  * @param cfg
1267  * @param section
1268  * @param subsect
1269  * @return 
1270  */
1271 expublic int ndrx_inicfg_get_subsect(ndrx_inicfg_t *cfg, 
1272         char **resources, char *section, ndrx_inicfg_section_keyval_t **out)
1273 {
1274     API_ENTRY;
1275     
1276     return ndrx_inicfg_get_subsect_int(cfg, resources, section, out);
1277 }
1278 
1279 /**
1280  * Iterate over the sections & return the matched image
1281  * We might want to return multiple hashes here of the sections found.
1282  * API version of _ndrx_inicfg_iterate
1283  * @param cfg
1284  * @param fullsection_starts_with
1285  * @return List of sections
1286  */
1287 expublic int ndrx_inicfg_iterate(ndrx_inicfg_t *cfg, 
1288         char **resources,
1289         char **section_start_with, 
1290         ndrx_inicfg_section_t **out)
1291 {
1292     return _ndrx_inicfg_iterate(cfg, resources, section_start_with, out);
1293 }
1294 
1295 /**
1296  * Free all sections from hash
1297  * API version of _ndrx_inicfg_sections_free
1298  * @param sections ptr becomes invalid after function call
1299  */
1300 expublic void ndrx_inicfg_sections_free(ndrx_inicfg_section_t *sections)
1301 {
1302     API_ENTRY;
1303     _ndrx_inicfg_sections_free(sections);
1304 }
1305 
1306 /**
1307  * Free the memory of file (API version of _ndrx_inicfg_file_free)
1308  * API version of 
1309  * @param cfg
1310  * @param fullfile
1311  * @return 
1312  */
1313 expublic void ndrx_inicfg_file_free(ndrx_inicfg_t *cfg, ndrx_inicfg_file_t *cfgfile)
1314 {
1315     API_ENTRY;
1316     _ndrx_inicfg_file_free(cfg, cfgfile);
1317 }
1318 
1319 
1320 /**
1321  * Free the whole config
1322  * API version of _ndrx_inicfg_free
1323  * @param cfg config handler will become invalid after this operation
1324  * @return 
1325  */
1326 expublic void ndrx_inicfg_free(ndrx_inicfg_t *cfg)
1327 {
1328     API_ENTRY;
1329     _ndrx_inicfg_free(cfg);
1330 }
1331 
1332 /* vim: set ts=4 sw=4 et smartindent: */