From: Aaron Taylor Date: Sat, 21 Sep 2019 12:50:31 +0000 (-0700) Subject: Replaced homebrew config file parsing with iniparser library. X-Git-Url: http://git.subgeniuskitty.com/icmpmonitor/.git/commitdiff_plain/33858ce29a9e9d3d7085e90aa0d0ed65dad93977 Replaced homebrew config file parsing with iniparser library. Updated and simplified Makefile. Updated and consolidated LICENSE. --- diff --git a/LICENSE b/LICENSE index e418a86..73ffe31 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,56 @@ -ICMPMONITOR.C +MIT/X Consortium License -Using the InterNet Control Message Protocol (ICMP) "ECHO" facility, -monitor several hosts, and notify admin if some of them are down. +© 2019 Aaron Taylor + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +================================================================================ + +This license applies to the four files under `iniparser/`. + +Copyright (c) 2000-2011 by Nicolas Devillard. +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +================================================================================ + +The following two licenses were included at the top of `icmpmonitor.c` during +the initial code import. + +-------------------------------------------------------------------------------- Author - Vadim Zaliva @@ -9,7 +58,7 @@ Author - Status - Public Domain. Distribution Unlimited. -================================================================================ +-------------------------------------------------------------------------------- Copyright (c) 1989 The Regents of the University of California. All rights reserved. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0142f6e --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# © 2018 Aaron Taylor +# See LICENSE file for copyright and license details. + +#################################################################################################### +# Executables + +CC = cc + +#################################################################################################### +# Configuration + +CC_FLAGS = -fPIC -Wall -pedantic -O2 +SRC_FILES = icmpmonitor.c iniparser/dictionary.c iniparser/iniparser.c + +#################################################################################################### +# Targets + +all: icmpmonitor + +icmpmonitor: + $(CC) $(CC_FLAGS) -o $@ $(SRC_FILES) + +clean: + @rm -f icmpmonitor icmpmonitor.core diff --git a/cfg.c b/cfg.c deleted file mode 100644 index 72f30a1..0000000 --- a/cfg.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * $Id: cfg.c,v 1.1.1.1 1999/11/21 08:16:12 lord Exp $ - * - * Vadim Zaliva - * http://www.crocodile.org/ - * - * Copyright (C) 1999 Vadim Zaliva - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "cfg.h" -#include -#include -#include -#include -#include - -#define MAXTOKENLEN 1024 - -static int cfg_entry_cmp(const void *a,const void *b) -{ - return(strcmp((*((struct Dict **)a))->name, (*((struct Dict **)b))->name)); -} - -static int cfg_entry_match(const void *a,const void *b) -{ - return(strcmp((const char *)a, (*((struct Dict **)b))->name)); -} - -char *cfgfind(const char *name,struct Cfg *cfg, int offset) -{ - int j; - struct Dict **res; - - res=(struct Dict **)bsearch(name, - cfg->dict, - cfg->nelements, - sizeof(struct Dict *), - cfg_entry_match - ); - - if(!res) - return NULL; - - if(offset>=(*res)->nvalues) - return NULL; - - return (*res)->value[offset]; -} - -int writecfg(const char *name,struct Cfg *cfg) -{ - FILE *f; - int j; - int i; - - if((f=fopen(name,"wb"))==NULL) - return -1; - - if(cfg) - { - for(i=0;inelements;i++) - { - if(!cfg->dict[i]->name) - continue; - fprintf(f,"%s\t",cfg->dict[i]->name); - for(j=0;jdict[i]->nvalues;j++) - if(cfg->dict[i]->value[j]) - fprintf(f," %s",cfg->dict[i]->value[j]); //TODO: quote values with spaces. - fprintf(f,"\n"); - } - } - fclose(f); - return 0; -} - -void freecfg(struct Cfg *cfg) -{ - int i,j; - - if(!cfg) - return; - - for(i=0;inelements;i++) - { - if(cfg->dict[i]->value) - { - for(j=0;jdict[i]->nvalues;j++) - if(cfg->dict[i]->value[j]) - free(cfg->dict[i]->value[j]); - free(cfg->dict[i]->value); - } - - if(cfg->dict[i]->name) - free(cfg->dict[i]->name); - } - - free(cfg->dict); - free(cfg); -} - -struct Cfg *readcfg(const char *name) -{ - struct Cfg *cfg; - FILE *f; - char tmp[MAXTOKENLEN]; - char *s; - int c; - - enum - { - START, - NAME, - VALUE, - INQUOTE, - WHITESPACE, - COMMENT - } state=START; - - int n,i; - char *pname,*pvalue; - - if((f=fopen(name,"rb"))==NULL) - return NULL; - - cfg=malloc(sizeof(struct Cfg)); - cfg->nelements=0; - s=tmp; - - while((c=fgetc(f))!=EOF) - { - /* Order of 'case' statements is important here! */ - switch(state) - { - case START: - if(c=='#') - { - state=COMMENT; - break; - } - else - if(!isspace(c)) - { - s=tmp; - state=NAME; - } else - { - break; - } - - case NAME: - if(isspace(c)) - { - struct Dict *tmp1=malloc(sizeof(struct Dict)); - *s='\0'; - tmp1->nvalues = 0; - tmp1->value = NULL; - tmp1->name = strdup(tmp); - - cfg_add_entry(cfg, tmp1); - - state=WHITESPACE; - } - else - { - *s++=c; - if(s==(tmp+sizeof(tmp))) - { - /* internal buffer overflow */ - freecfg(cfg); - return NULL; - } - } - break; - - case WHITESPACE: - if(c=='\n') - { - state=START; - break; - } - else - { - if(!isspace(c)) - { - s=tmp; - state=VALUE; - } else - { - break; - } - } - - case VALUE: - if(c=='"') - state=INQUOTE; - else - if(isspace(c)) - { - struct Dict *last=cfg->dict[cfg->nelements-1]; - char **tmp1; - int i; - - *s='\0'; - tmp1=last->value; - last->value=malloc((last->nvalues+1)*sizeof(char *)); - if(tmp1) - { - for(i=0;invalues;i++) - last->value[i]=tmp1[i]; - free(tmp1); - } - last->value[last->nvalues]=strdup(tmp); - last->nvalues++; - if(c=='\n') - state=START; - else - state=WHITESPACE; - } else - { - *s++=c; - if(s==(tmp+sizeof(tmp))) - { - /* internal buffer overflow */ - freecfg(cfg); - return NULL; - } - } - break; - - case INQUOTE: - if(c=='"') - { - state=VALUE; - } - else - { - *s++=c; - if(s==(tmp+sizeof(tmp))) - { - /* internal buffer overflow */ - freecfg(cfg); - return NULL; - } - } - break; - - case COMMENT: - if(c=='\n') - state=START; - break; - } - } - - sortcfg(cfg); - return cfg; -} - -/** - * Sorts cfg. - * Should be called after each modification - * before attempting to retrieve any data. - */ -void sortcfg (struct Cfg *cfg) -{ - qsort((void *) cfg->dict, - cfg->nelements, - sizeof(struct Dict *), - cfg_entry_cmp); - -} - -/** - * Adds new cfg entry to the end of the dictionary. - * you need to call sortcfg() before it could be - * really used. - */ -void cfg_add_entry (struct Cfg *cfg, struct Dict *d) -{ - if(cfg->nelements) - { - struct Dict **last=cfg->dict; - cfg->dict=malloc(sizeof(struct Dict *)*(cfg->nelements+1)); - memcpy(cfg->dict,last,sizeof(struct Dict *)*cfg->nelements); - cfg->dict[cfg->nelements]=d; - cfg->nelements++; - free(last); - } - else - { - cfg->dict = malloc(sizeof(struct Dict *)); - cfg->dict[0] = d; - cfg->nelements = 1; - } -} - - -/** - * Adds entry with given name and list of values. - * list should be terminated with NULL and contain - * only const char pointers. - */ -void cfg_new_entry(struct Cfg *cfg, const char *name, ...) -{ - int n; - va_list ap; - struct Dict *tmp=malloc(sizeof(struct Dict)); - - tmp->name = strdup(name); - - va_start(ap,name); - n=0; - while(va_arg(ap, const char *)) n++; - va_end(ap); - - tmp->nvalues = n; - if(n) - { - int i; - - va_start(ap,name); - tmp->value = malloc(n*sizeof(char *)); - for(i=0;ivalue[i] = strdup(va_arg(ap, const char *)); - va_end(ap); - } else - { - tmp->value = NULL; - } - - cfg_add_entry(cfg, tmp); -} - -void cfg_new_ulong_entry (struct Cfg *cfg, const char *name, unsigned long v) -{ - char tmp[80]; - sprintf(tmp,"%lu",v); - cfg_new_entry(cfg, name, tmp, NULL); -} - -/** - * add long extended to 'w' chars, with added trailing zeros. - * - * @param v - field value - * @param w - field width - */ -void cfg_new_fmt_ulong_entry (struct Cfg *cfg, const char *name, unsigned long v, int w) -{ - char tmp[80]; - sprintf(tmp,"%0*lu",w, v); - cfg_new_entry(cfg, name, tmp, NULL); -} - -struct Cfg *newcfg () -{ - struct Cfg *res=malloc(sizeof(struct Cfg)); - res->nelements = 0; - return res; -} diff --git a/cfg.h b/cfg.h deleted file mode 100644 index 3df5d9c..0000000 --- a/cfg.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * $Id: cfg.h,v 1.1.1.1 1999/11/21 08:16:12 lord Exp $ - * - * Vadim Zaliva - * http://www.crocodile.org/ - * - * Copyright (C) 1999 Vadim Zaliva - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -struct Dict -{ - char *name; - char **value; - int nvalues; -}; - -struct Cfg -{ - struct Dict **dict; - int nelements ; -}; - -struct Cfg *readcfg (const char *filename); -int writecfg (const char * filename, struct Cfg *); -char *cfgfind (const char *,struct Cfg *, int offset); -void freecfg (struct Cfg *); -struct Cfg *newcfg (); -void sortcfg (struct Cfg *); -void cfg_add_entry (struct Cfg *, struct Dict *); - -/* convinience functions */ -void cfg_new_entry (struct Cfg *cfg, const char *name, ...); -void cfg_new_ulong_entry (struct Cfg *cfg, const char *name, unsigned long v); -void cfg_new_fmt_ulong_entry (struct Cfg *cfg, const char *name, unsigned long v, int w); - - diff --git a/icmpmonitor.c b/icmpmonitor.c index 98b6f5e..4352416 100644 --- a/icmpmonitor.c +++ b/icmpmonitor.c @@ -1,5 +1,6 @@ /* * Monitor hosts using ICMP "echo" and notify when down. + * TODO: Write a better description. * * © 2019 Aaron Taylor * © 1999 Vadim Zaliva @@ -23,12 +24,15 @@ #include #include #include - -#include "cfg.h" +#include "iniparser/iniparser.h" #define MAXPACKETSIZE (65536 - 60 - 8) /* TODO: What are the magic numbers? */ #define DEFAULTDATALEN (64 - 8) /* TODO: What are the magic numbers? */ +/* Must be larger than the length of the longest configuration key (currently 'start_condition'). */ +#define MAXCONFKEYLEN 20 + +/* One struct per host as listed in the config file. */ struct monitor_host { /* From the config file */ char * name; @@ -269,65 +273,77 @@ get_response(void) } } +/* + * Parses `config_file` and creates relevant entries under the global variable `hosts`. + */ static void -read_hosts(const char * cfg_file_name) +parse_config(const char * conf_file) { - int i, n = 0; - struct Cfg * cfg; + dictionary * conf = iniparser_load(conf_file); + if (conf == NULL) { + fprintf(stderr, "ERROR: Unable to parse configuration file %s.\n", conf_file); + exit(EXIT_FAILURE); + } - if ((cfg = readcfg(cfg_file_name)) == NULL) { - fprintf(stderr, "ERROR: Failed to read config.\n"); + int host_count = iniparser_getnsec(conf); + if (host_count < 1 ) { + fprintf(stderr, "ERROR: Unable to determine number of hosts in configuration file.\n"); exit(EXIT_FAILURE); } - if (cfg->nelements) { - hosts = malloc(sizeof(struct monitor_host *) * cfg->nelements); - for (i = 0; i < cfg->nelements; i++) { - if (cfg->dict[i]->nvalues < 4) { - fprintf(stderr, "ERROR: Not enough fields in record %d of cfg file. Got %d.\n", n, cfg->dict[i]->nvalues+1); - exit(EXIT_FAILURE); - } else if (cfg->dict[i]->nvalues>5) { - fprintf(stderr, "ERROR: Too many fields in record %d of cfg file. Got %d.\n", n, cfg->dict[i]->nvalues+1); - exit(EXIT_FAILURE); + hosts = malloc(sizeof(struct monitor_host *) * host_count); + for (int i=0; i < host_count; i++) { + /* Allocate a buffer large enough to hold the full 'section:key' string. */ + int section_len = strlen(iniparser_getsecname(conf, i)); + char * key_buf = malloc(section_len + 1 + MAXCONFKEYLEN + 1); + strcpy(key_buf, iniparser_getsecname(conf, i)); + key_buf[section_len++] = ':'; + + hosts[i] = malloc(sizeof(struct monitor_host)); + for (int k=0; k<6; k++) { + key_buf[section_len] = '\0'; /* Reuse the section name and colon on each pass through this loop. */ + switch (k) { + case 0: + strncat(key_buf, "host", MAXCONFKEYLEN); + hosts[i]->name = strdup(iniparser_getstring(conf, key_buf, NULL)); + break; + case 1: + strncat(key_buf, "interval", MAXCONFKEYLEN); + hosts[i]->ping_interval = iniparser_getint(conf, key_buf, -1); + break; + case 2: + strncat(key_buf, "max_delay", MAXCONFKEYLEN); + hosts[i]->max_delay = iniparser_getint(conf, key_buf, -1); + break; + case 3: + strncat(key_buf, "up_cmd", MAXCONFKEYLEN); + hosts[i]->upcmd = strdup(iniparser_getstring(conf, key_buf, NULL)); + break; + case 4: + strncat(key_buf, "down_cmd", MAXCONFKEYLEN); + hosts[i]->downcmd = strdup(iniparser_getstring(conf, key_buf, NULL)); + break; + case 5: + /* TODO: Parse for up/down/auto in start_condition. */ + /* TODO: Do a host up/down check if necessary. */ + hosts[i]->hostup = true; + break; } - - hosts[n] = malloc(sizeof(struct monitor_host)); - hosts[n]->name = strdup(cfg->dict[i]->name); - hosts[n]->ping_interval = atoi (cfg->dict[i]->value[0]); - hosts[n]->max_delay = atoi (cfg->dict[i]->value[1]); - hosts[n]->upcmd = strdup(cfg->dict[i]->value[2]); - hosts[n]->downcmd = strdup(cfg->dict[i]->value[3]); - - if (cfg->dict[i]->nvalues == 4) { - hosts[n]->hostup = true; - } else if (strcmp(cfg->dict[i]->value[4], "up") == 0) { - hosts[n]->hostup = true; - } else if (strcmp(cfg->dict[i]->value[4], "down") == 0) { - hosts[n]->hostup = false; - } else if (strcmp(cfg->dict[i]->value[4], "auto") == 0) { - /* TODO: Send a ping and set initial state accordingly. */ - } else { - fprintf(stderr, "ERROR: Illegal value %s in record %d for startup condition.\n", cfg->dict[i]->value[4], n); - exit(EXIT_FAILURE); } - hosts[n]->sentpackets = 0; - hosts[n]->recvdpackets = 0; - - hosts[n]->socket = -1; - hosts[n]->next = NULL; - if (n > 0) hosts[n-1]->next=hosts[n]; - gettimeofday(&(hosts[n]->last_ping_received), (struct timezone *)NULL); - - n++; + /* TODO: Do I want to make any checks for up_cmd, down_cmd, and start_condition? */ + if (hosts[i]->name == NULL || hosts[i]->ping_interval == -1 || hosts[i]->max_delay == -1) { + fprintf(stderr, "ERROR: Problems parsing section %s.\n", iniparser_getsecname(conf, i)); + exit(EXIT_FAILURE); } - } - - freecfg(cfg); - if (n <= 0) { - fprintf(stderr, "ERROR: No hosts defined in cfg file, exiting.\n"); - exit(EXIT_FAILURE); + hosts[i]->sentpackets = 0; + hosts[i]->recvdpackets = 0; + hosts[i]->socket = -1; + hosts[i]->next = NULL; + if (i>0) hosts[i-1]->next = hosts[i]; + gettimeofday(&(hosts[i]->last_ping_received), (struct timezone *) NULL); } + iniparser_freedict(conf); } static int @@ -389,14 +405,16 @@ init_hosts(void) } } -int -main(int argc, char ** argv) +void +print_usage(void) { - extern char * optarg; - extern int optind; - char * cfgfile = NULL; - int param; + fprintf(stderr,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n"); +} +void +parse_params(int argc, char ** argv) +{ + int param; while ((param = getopt(argc, argv, "rvf:")) != -1) { switch(param) { case 'v': @@ -406,20 +424,23 @@ main(int argc, char ** argv) retry_down_cmd = true; break; case 'f': - cfgfile=strdup(optarg); + parse_config(optarg); break; default: - fprintf(stderr,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n"); + print_usage(); exit(EXIT_FAILURE); } } - - if (!cfgfile) { - fprintf(stderr, "ERROR: No config file specified.\n"); + if (hosts == NULL) { + fprintf(stderr, "ERROR: Unable to parse a config file.\n"); exit(EXIT_FAILURE); } +} - read_hosts(cfgfile); +int +main(int argc, char ** argv) +{ + parse_params(argc, argv); init_hosts(); diff --git a/icmpmonitor.ini b/icmpmonitor.ini new file mode 100644 index 0000000..5886b33 --- /dev/null +++ b/icmpmonitor.ini @@ -0,0 +1,15 @@ +[kryten.subgeniuskitty.com] +host = kryten.subgeniuskitty.com +interval = 1 +max_delay = 10 +up_cmd = "echo UP: kryten.SGK" +down_cmd = "echo DOWN: kryten.SGK" +start_condition = up + +[talisker.subgeniuskitty.com] +host = talisker.subgeniuskitty.com +interval = 1 +max_delay = 10 +up_cmd = "echo UP: talisker.SGK" +down_cmd = "echo DOWN: talisker.SGK" +start_condition = down diff --git a/iniparser/dictionary.c b/iniparser/dictionary.c new file mode 100644 index 0000000..cb7ccd4 --- /dev/null +++ b/iniparser/dictionary.c @@ -0,0 +1,380 @@ +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.c + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ +#include "dictionary.h" + +#include +#include +#include +#include + +/** Maximum value size for integers and doubles. */ +#define MAXVALSZ 1024 + +/** Minimal allocated number of entries in a dictionary */ +#define DICTMINSZ 128 + +/** Invalid key token */ +#define DICT_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private functions + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Double the size of the dictionary + @param d Dictionary to grow + @return This function returns non-zero in case of failure + */ +/*--------------------------------------------------------------------------*/ +static int dictionary_grow(dictionary * d) +{ + char ** new_val ; + char ** new_key ; + unsigned * new_hash ; + + new_val = (char**) calloc(d->size * 2, sizeof *d->val); + new_key = (char**) calloc(d->size * 2, sizeof *d->key); + new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash); + if (!new_val || !new_key || !new_hash) { + /* An allocation failed, leave the dictionary unchanged */ + if (new_val) + free(new_val); + if (new_key) + free(new_key); + if (new_hash) + free(new_hash); + return -1 ; + } + /* Initialize the newly allocated space */ + memcpy(new_val, d->val, d->size * sizeof(char *)); + memcpy(new_key, d->key, d->size * sizeof(char *)); + memcpy(new_hash, d->hash, d->size * sizeof(unsigned)); + /* Delete previous data */ + free(d->val); + free(d->key); + free(d->hash); + /* Actually update the dictionary */ + d->size *= 2 ; + d->val = new_val; + d->key = new_key; + d->hash = new_hash; + return 0 ; +} + +/*--------------------------------------------------------------------------- + Function codes + ---------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key) +{ + size_t len ; + unsigned hash ; + size_t i ; + + if (!key) + return 0 ; + + len = strlen(key); + for (hash=0, i=0 ; i>6) ; + } + hash += (hash <<3); + hash ^= (hash >>11); + hash += (hash <<15); + return hash ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary objet. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*-------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size) +{ + dictionary * d ; + + /* If no size was specified, allocate space for DICTMINSZ */ + if (sizesize = size ; + d->val = (char**) calloc(size, sizeof *d->val); + d->key = (char**) calloc(size, sizeof *d->key); + d->hash = (unsigned*) calloc(size, sizeof *d->hash); + } + return d ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * d) +{ + ssize_t i ; + + if (d==NULL) return ; + for (i=0 ; isize ; i++) { + if (d->key[i]!=NULL) + free(d->key[i]); + if (d->val[i]!=NULL) + free(d->val[i]); + } + free(d->val); + free(d->key); + free(d->hash); + free(d); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def) +{ + unsigned hash ; + ssize_t i ; + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + return d->val[i] ; + } + } + } + return def ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * d, const char * key, const char * val) +{ + ssize_t i ; + unsigned hash ; + + if (d==NULL || key==NULL) return -1 ; + + /* Compute hash for this key */ + hash = dictionary_hash(key) ; + /* Find if value is already in dictionary */ + if (d->n>0) { + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (hash==d->hash[i]) { /* Same hash value */ + if (!strcmp(key, d->key[i])) { /* Same key */ + /* Found a value: modify and return */ + if (d->val[i]!=NULL) + free(d->val[i]); + d->val[i] = (val ? xstrdup(val) : NULL); + /* Value has been modified: return */ + return 0 ; + } + } + } + } + /* Add a new value */ + /* See if dictionary needs to grow */ + if (d->n==d->size) { + /* Reached maximum size: reallocate dictionary */ + if (dictionary_grow(d) != 0) + return -1; + } + + /* Insert key in the first empty slot. Start at d->n and wrap at + d->size. Because d->n < d->size this will necessarily + terminate. */ + for (i=d->n ; d->key[i] ; ) { + if(++i == d->size) i = 0; + } + /* Copy key */ + d->key[i] = xstrdup(key); + d->val[i] = (val ? xstrdup(val) : NULL) ; + d->hash[i] = hash; + d->n ++ ; + return 0 ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key) +{ + unsigned hash ; + ssize_t i ; + + if (key == NULL || d == NULL) { + return; + } + + hash = dictionary_hash(key); + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + /* Found key */ + break ; + } + } + } + if (i>=d->size) + /* Key not found */ + return ; + + free(d->key[i]); + d->key[i] = NULL ; + if (d->val[i]!=NULL) { + free(d->val[i]); + d->val[i] = NULL ; + } + d->hash[i] = 0 ; + d->n -- ; + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out) +{ + ssize_t i ; + + if (d==NULL || out==NULL) return ; + if (d->n<1) { + fprintf(out, "empty dictionary\n"); + return ; + } + for (i=0 ; isize ; i++) { + if (d->key[i]) { + fprintf(out, "%20s\t[%s]\n", + d->key[i], + d->val[i] ? d->val[i] : "UNDEF"); + } + } + return ; +} diff --git a/iniparser/dictionary.h b/iniparser/dictionary.h new file mode 100644 index 0000000..d04b6ce --- /dev/null +++ b/iniparser/dictionary.h @@ -0,0 +1,173 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.h + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _DICTIONARY_H_ +#define _DICTIONARY_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*--------------------------------------------------------------------------- + New types + ---------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dictionary object + + This object contains a list of string/string associations. Each + association is identified by a unique string key. Looking up values + in the dictionary is speeded up by the use of a (hopefully collision-free) + hash function. + */ +/*-------------------------------------------------------------------------*/ +typedef struct _dictionary_ { + int n ; /** Number of entries in dictionary */ + ssize_t size ; /** Storage size */ + char ** val ; /** List of string values */ + char ** key ; /** List of string keys */ + unsigned * hash ; /** List of hash values for keys */ +} dictionary ; + + +/*--------------------------------------------------------------------------- + Function prototypes + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key); + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary objet. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*--------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * vd); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * vd, const char * key, const char * val); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/iniparser/iniparser.c b/iniparser/iniparser.c new file mode 100644 index 0000000..f1d1658 --- /dev/null +++ b/iniparser/iniparser.c @@ -0,0 +1,836 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.c + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ +/*---------------------------- Includes ------------------------------------*/ +#include +#include +#include "iniparser.h" + +/*---------------------------- Defines -------------------------------------*/ +#define ASCIILINESZ (1024) +#define INI_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private to this module + ---------------------------------------------------------------------------*/ +/** + * This enum stores the status for each parsed line (internal use only). + */ +typedef enum _line_status_ { + LINE_UNPROCESSED, + LINE_ERROR, + LINE_EMPTY, + LINE_COMMENT, + LINE_SECTION, + LINE_VALUE +} line_status ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Convert a string to lowercase. + @param in String to convert. + @param out Output buffer. + @param len Size of the out buffer. + @return ptr to the out buffer or NULL if an error occured. + + This function convert a string into lowercase. + At most len - 1 elements of the input string will be converted. + */ +/*--------------------------------------------------------------------------*/ +static const char * strlwc(const char * in, char *out, unsigned len) +{ + unsigned i ; + + if (in==NULL || out == NULL || len==0) return NULL ; + i=0 ; + while (in[i] != '\0' && i < len-1) { + out[i] = (char)tolower((int)in[i]); + i++ ; + } + out[i] = '\0'; + return out ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Remove blanks at the beginning and the end of a string. + @param str String to parse and alter. + @return unsigned New size of the string. + */ +/*--------------------------------------------------------------------------*/ +static unsigned strstrip(char * s) +{ + char *last = NULL ; + char *dest = s; + + if (s==NULL) return 0; + + last = s + strlen(s); + while (isspace((int)*s) && *s) s++; + while (last > s) { + if (!isspace((int)*(last-1))) + break ; + last -- ; + } + *last = (char)0; + + memmove(dest,s,last - s + 1); + return last - s; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Default error callback for iniparser: wraps `fprintf(stderr, ...)`. + */ +/*--------------------------------------------------------------------------*/ +static int default_error_callback(const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + ret = vfprintf(stderr, format, argptr); + va_end(argptr); + return ret; +} + +static int (*iniparser_error_callback)(const char*, ...) = default_error_callback; + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_set_error_callback(int (*errback)(const char *, ...)) +{ + if (errback) { + iniparser_error_callback = errback; + } else { + iniparser_error_callback = default_error_callback; + } +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getnsec(const dictionary * d) +{ + int i ; + int nsec ; + + if (d==NULL) return -1 ; + nsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + nsec ++ ; + } + } + return nsec ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getsecname(const dictionary * d, int n) +{ + int i ; + int foundsec ; + + if (d==NULL || n<0) return NULL ; + foundsec=0 ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + foundsec++ ; + if (foundsec>n) + break ; + } + } + if (foundsec<=n) { + return NULL ; + } + return d->key[i] ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f) +{ + int i ; + + if (d==NULL || f==NULL) return ; + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + if (d->val[i]!=NULL) { + fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); + } else { + fprintf(f, "[%s]=UNDEF\n", d->key[i]); + } + } + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump_ini(const dictionary * d, FILE * f) +{ + int i ; + int nsec ; + const char * secname ; + + if (d==NULL || f==NULL) return ; + + nsec = iniparser_getnsec(d); + if (nsec<1) { + /* No section in file: dump all keys as they are */ + for (i=0 ; isize ; i++) { + if (d->key[i]==NULL) + continue ; + fprintf(f, "%s = %s\n", d->key[i], d->val[i]); + } + return ; + } + for (i=0 ; isize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + fprintf(f, + "%-30s = %s\n", + d->key[j]+seclen+1, + d->val[j] ? d->val[j] : ""); + } + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s) +{ + int seclen, nkeys ; + char keym[ASCIILINESZ+1]; + int j ; + + nkeys = 0; + + if (d==NULL) return nkeys; + if (! iniparser_find_entry(d, s)) return nkeys; + + seclen = (int)strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) + nkeys++; + } + + return nkeys; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys) +{ + int i, j, seclen ; + char keym[ASCIILINESZ+1]; + + if (d==NULL || keys==NULL) return NULL; + if (! iniparser_find_entry(d, s)) return NULL; + + seclen = (int)strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + i = 0; + + for (j=0 ; jsize ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + keys[i] = d->key[j]; + i++; + } + } + + return keys; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def) +{ + const char * lc_key ; + const char * sval ; + char tmp_str[ASCIILINESZ+1]; + + if (d==NULL || key==NULL) + return def ; + + lc_key = strlwc(key, tmp_str, sizeof(tmp_str)); + sval = dictionary_get(d, lc_key, def); + return sval ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return long integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return strtol(str, NULL, 0); +} + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound) +{ + return (int)iniparser_getlongint(d, key, notfound); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==INI_INVALID_KEY) return notfound ; + return atof(str); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound) +{ + int ret ; + const char * c ; + + c = iniparser_getstring(d, key, INI_INVALID_KEY); + if (c==INI_INVALID_KEY) return notfound ; + if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { + ret = 1 ; + } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { + ret = 0 ; + } else { + ret = notfound ; + } + return ret; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) +{ + int found=0 ; + if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { + found = 1 ; + } + return found ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val) +{ + char tmp_str[ASCIILINESZ+1]; + return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry) +{ + char tmp_str[ASCIILINESZ+1]; + dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str))); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Load a single line from an INI file + @param input_line Input line, may be concatenated multi-line input + @param section Output space to store section + @param key Output space to store key + @param value Output space to store value + @return line_status value + */ +/*--------------------------------------------------------------------------*/ +static line_status iniparser_line( + const char * input_line, + char * section, + char * key, + char * value) +{ + line_status sta ; + char * line = NULL; + size_t len ; + + line = xstrdup(input_line); + len = strstrip(line); + + sta = LINE_UNPROCESSED ; + if (len<1) { + /* Empty line */ + sta = LINE_EMPTY ; + } else if (line[0]=='#' || line[0]==';') { + /* Comment line */ + sta = LINE_COMMENT ; + } else if (line[0]=='[' && line[len-1]==']') { + /* Section name */ + sscanf(line, "[%[^]]", section); + strstrip(section); + strlwc(section, section, len); + sta = LINE_SECTION ; + } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 + || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2) { + /* Usual key=value with quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + /* Don't strip spaces from values surrounded with quotes */ + sta = LINE_VALUE ; + } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { + /* Usual key=value without quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + strstrip(value); + /* + * sscanf cannot handle '' or "" as empty values + * this is done here + */ + if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { + value[0]=0 ; + } + sta = LINE_VALUE ; + } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 + || sscanf(line, "%[^=] %[=]", key, value) == 2) { + /* + * Special cases: + * key= + * key=; + * key=# + */ + strstrip(key); + strlwc(key, key, len); + value[0]=0 ; + sta = LINE_VALUE ; + } else { + /* Generate syntax error */ + sta = LINE_ERROR ; + } + + free(line); + return sta ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame) +{ + FILE * in ; + + char line [ASCIILINESZ+1] ; + char section [ASCIILINESZ+1] ; + char key [ASCIILINESZ+1] ; + char tmp [(ASCIILINESZ * 2) + 2] ; + char val [ASCIILINESZ+1] ; + + int last=0 ; + int len ; + int lineno=0 ; + int errs=0; + int mem_err=0; + + dictionary * dict ; + + if ((in=fopen(ininame, "r"))==NULL) { + iniparser_error_callback("iniparser: cannot open %s\n", ininame); + return NULL ; + } + + dict = dictionary_new(0) ; + if (!dict) { + fclose(in); + return NULL ; + } + + memset(line, 0, ASCIILINESZ); + memset(section, 0, ASCIILINESZ); + memset(key, 0, ASCIILINESZ); + memset(val, 0, ASCIILINESZ); + last=0 ; + + while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { + lineno++ ; + len = (int)strlen(line)-1; + if (len<=0) + continue; + /* Safety check against buffer overflows */ + if (line[len]!='\n' && !feof(in)) { + iniparser_error_callback( + "iniparser: input line too long in %s (%d)\n", + ininame, + lineno); + dictionary_del(dict); + fclose(in); + return NULL ; + } + /* Get rid of \n and spaces at end of line */ + while ((len>=0) && + ((line[len]=='\n') || (isspace(line[len])))) { + line[len]=0 ; + len-- ; + } + if (len < 0) { /* Line was entirely \n and/or spaces */ + len = 0; + } + /* Detect multi-line */ + if (line[len]=='\\') { + /* Multi-line value */ + last=len ; + continue ; + } else { + last=0 ; + } + switch (iniparser_line(line, section, key, val)) { + case LINE_EMPTY: + case LINE_COMMENT: + break ; + + case LINE_SECTION: + mem_err = dictionary_set(dict, section, NULL); + break ; + + case LINE_VALUE: + sprintf(tmp, "%s:%s", section, key); + mem_err = dictionary_set(dict, tmp, val); + break ; + + case LINE_ERROR: + iniparser_error_callback( + "iniparser: syntax error in %s (%d):\n-> %s\n", + ininame, + lineno, + line); + errs++ ; + break; + + default: + break ; + } + memset(line, 0, ASCIILINESZ); + last=0; + if (mem_err<0) { + iniparser_error_callback("iniparser: memory allocation failure\n"); + break ; + } + } + if (errs) { + dictionary_del(dict); + dict = NULL ; + } + fclose(in); + return dict ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d) +{ + dictionary_del(d); +} diff --git a/iniparser/iniparser.h b/iniparser/iniparser.h new file mode 100644 index 0000000..37ff7b7 --- /dev/null +++ b/iniparser/iniparser.h @@ -0,0 +1,358 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.h + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _INIPARSER_H_ +#define _INIPARSER_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include +#include +#include + +/* + * The following #include is necessary on many Unixes but not Linux. + * It is not needed for Windows platforms. + * Uncomment it if needed. + */ +/* #include */ + +#include "dictionary.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_set_error_callback(int (*errback)(const char *, ...)); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ + +int iniparser_getnsec(const dictionary * d); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ + +const char * iniparser_getsecname(const dictionary * d, int n); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dump_ini(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry); + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame); + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sample.cfg b/sample.cfg deleted file mode 100644 index 32a5128..0000000 --- a/sample.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# $Id: sample.cfg,v 1.2 2000/07/25 01:57:01 lord Exp $ -# -#Name, ping_interval, max_delay, upcmd, downcmd, startup condition - -# Monitor these hosts when they go down -r90 1 5 "echo r90 up| mail lord" "echo r90 down| mail lord" -noir.crocodile.org 60 10 "echo noir up| mail lord" "echo noir down| mail lord" -crocodile.org 60 10 "echo home up| mail lord" "echo home down| mail lord" -lizard 1 5 "echo lizard up| mail lord" "echo lizard down| mail lord" - -# Monitor this host when it is started -windoze 60 10 "echo warning windoze is running| mail lord" "echo internet clean again| mail lord" down - -# If and only if the fast gateway goes down switch to another, slower one. -# Assume no valid configuration on startup. -gate.ether 1 5 "ifup -s ether" "ifup -s phone" none