File ini_parser.c¶
File List > base > ini_parser.c
Go to the documentation of this file
/*
* Copyright (c) 2000-2007 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.
*/
#include "config.h"
#include "porting.h"
#include "ini_parser.h"
#include "chartype.h"
#include "error_manager.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"
#define INI_BUFSIZ (512)
#define INI_INVALID_KEY ((char*)-1)
enum ini_line_status
{
LINE_UNPROCESSED,
LINE_ERROR,
LINE_EMPTY,
LINE_COMMENT,
LINE_SECTION,
LINE_VALUE
};
typedef enum ini_line_status INI_LINE_STATUS;
static void *ini_dblalloc (void *ptr, int size);
static unsigned int ini_table_hash (char *key);
static INI_TABLE *ini_table_new (int size);
static void ini_table_free (INI_TABLE * vd);
static const char *ini_table_get (INI_TABLE * ini, char *key, const char *def, int *lineno);
static int ini_table_set (INI_TABLE * vd, char *key, char *val, int lineno);
#if defined (ENABLE_UNUSED_FUNCTION)
static void ini_table_unset (INI_TABLE * ini, char *key);
#endif /* ENABLE_UNUSED_FUNCTION */
static char *ini_str_lower (const char *s);
static char *ini_str_trim (char *s);
static INI_LINE_STATUS ini_parse_line (char *input_line, char *section, char *key, char *value);
static const char *ini_get_internal (INI_TABLE * ini, const char *key, const char *def, int *lineno);
/*
* ini_dblalloc() - Doubles the allocated size associated to a pointer
* return: new allocated pointer
* p(in): pointer
* size(in): current allocated size
*/
static void *
ini_dblalloc (void *p, int size)
{
void *newp;
newp = calloc (2 * size, 1);
if (newp == NULL)
{
return NULL;
}
memcpy (newp, p, size);
free (p);
return newp;
}
/*
* ini_table_hash() - Compute the hash key for a string
* return: hasn value
* key(in): Character string to use for key
* size(in): current allocated size
*
* Note: 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.
*/
static unsigned int
ini_table_hash (char *key)
{
size_t len;
size_t i;
unsigned int hash;
len = strlen (key);
for (hash = 0, i = 0; i < len; i++)
{
hash += (unsigned) key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
/*
* ini_table_new() - Create a new INI_TABLE object
* return: newly allocated INI_TABLE objet
* size(in): Optional initial size of the INI_TABLE
*
* Note: If you do not know in advance (roughly) the number of entries
* in the INI_TABLE, give size=0.
*/
static INI_TABLE *
ini_table_new (int size)
{
INI_TABLE *ini;
/* If no size was specified, allocate space for 128 */
if (size < 128)
{
size = 128;
}
ini = (INI_TABLE *) calloc (1, sizeof (INI_TABLE));
if (ini == NULL)
{
return NULL;
}
ini->size = size;
ini->val = (char **) calloc (size, sizeof (char *));
if (ini->val == NULL)
{
goto error;
}
ini->key = (char **) calloc (size, sizeof (char *));
if (ini->key == NULL)
{
goto error;
}
ini->lineno = (int *) calloc (size, sizeof (int));
if (ini->lineno == NULL)
{
goto error;
}
ini->hash = (unsigned int *) calloc (size, sizeof (unsigned int));
if (ini->hash == NULL)
{
goto error;
}
return ini;
error:
if (ini->hash != NULL)
{
free (ini->hash);
}
if (ini->lineno != NULL)
{
free (ini->lineno);
}
if (ini->key != NULL)
{
free (ini->key);
}
if (ini->val != NULL)
{
free (ini->val);
}
if (ini != NULL)
{
free (ini);
}
return NULL;
}
/*
* ini_table_free() - Delete a INI_TABLE object
* return: void
* ini(in): INI_TABLE object to deallocate
*
* Note:
*/
static void
ini_table_free (INI_TABLE * ini)
{
int i;
if (ini == NULL)
{
return;
}
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] != NULL)
{
free (ini->key[i]);
}
if (ini->val[i] != NULL)
{
free (ini->val[i]);
}
}
free (ini->val);
ini->val = NULL;
free (ini->key);
ini->key = NULL;
free (ini->lineno);
ini->lineno = NULL;
free (ini->hash);
ini->hash = NULL;
free (ini);
ini = NULL;
return;
}
/*
* ini_table_get() - Get a value from a INI_TABLE
* return: pointer to internally allocated character string
* ini(in): INI_TABLE object to search
* key(in): Key to look for in the INI_TABLE
* def(in): Default value to return if key not found
*
* Note: The returned character pointer points to data internal to the
* INI_TABLE object, you should not try to free it or modify it
*/
static const char *
ini_table_get (INI_TABLE * ini, char *key, const char *def, int *lineno)
{
unsigned int hash;
int i;
hash = ini_table_hash (key);
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
continue;
}
/* Compare hash */
if (hash == ini->hash[i])
{
/* Compare string, to avoid hash collisions */
if (!strcmp (key, ini->key[i]))
{
if (lineno)
{
*lineno = ini->lineno[i];
}
return ini->val[i];
}
}
}
return def;
}
/*
* ini_table_set() - Set a value in a INI_TABLE
* return: 0 if Ok, anything else otherwise
* ini(in): INI_TABLE object to modify
* key(in): Key to modify or add
* val(in): Value to add
* lineno(in): line number in ini file
*
* Note:
*/
static int
ini_table_set (INI_TABLE * ini, char *key, char *val, int lineno)
{
int i;
unsigned int hash;
if (ini == NULL || key == NULL)
{
return -1;
}
/* Compute hash for this key */
hash = ini_table_hash (key);
/* Find if value is already in INI_TABLE */
if (ini->n > 0)
{
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
continue;
}
if (hash == ini->hash[i])
{ /* Same hash value */
if (!strcmp (key, ini->key[i]))
{ /* Same key */
/* Found a value: modify and return */
if (ini->val[i] != NULL)
{
free (ini->val[i]);
}
ini->val[i] = val ? strdup (val) : NULL;
/* Value has been modified: return */
return 0;
}
}
}
}
/* Add a new value */
/* See if INI_TABLE needs to grow */
if (ini->n == ini->size)
{
/* Reached maximum size: reallocate INI_TABLE */
ini->val = (char **) ini_dblalloc (ini->val, ini->size * sizeof (char *));
ini->key = (char **) ini_dblalloc (ini->key, ini->size * sizeof (char *));
ini->lineno = (int *) ini_dblalloc (ini->lineno, ini->size * sizeof (int));
ini->hash = (unsigned int *) ini_dblalloc (ini->hash, ini->size * sizeof (unsigned int));
if ((ini->val == NULL) || (ini->key == NULL) || (ini->lineno == NULL) || (ini->hash == NULL))
{
/* Cannot grow INI_TABLE */
return -1;
}
/* Double size */
ini->size *= 2;
}
/* Insert key in the first empty slot */
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
/* Add key here */
break;
}
}
ini->n++;
ini->lineno[i] = lineno;
ini->hash[i] = hash;
ini->key[i] = strdup (key);
if (val == NULL)
{
ini->nsec++; /* section */
ini->val[i] = NULL;
}
else
{
ini->val[i] = strdup (val);
}
return 0;
}
#if defined (ENABLE_UNUSED_FUNCTION)
/*
* ini_table_unset() - Delete a key in a INI_TABLE
* return: void
* ini(in): INI_TABLE object to modify
* key(in): Key to remove
*
* Note:
*/
static void
ini_table_unset (INI_TABLE * ini, char *key)
{
unsigned int i, hash;
if (key == NULL)
{
return;
}
hash = ini_table_hash (key);
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
continue;
}
/* Compare hash */
if (hash == ini->hash[i])
{
/* Compare string, to avoid hash collisions */
if (!strcmp (key, ini->key[i]))
{
/* Found key */
break;
}
}
}
if (i >= ini->size)
{
/* Key not found */
return;
}
free (ini->key[i]);
ini->key[i] = NULL;
if (ini->val[i] != NULL)
{
free (ini->val[i]);
ini->val[i] = NULL;
}
ini->lineno[i] = EOF;
ini->hash[i] = 0;
ini->n--;
return;
}
#endif /* ENABLE_UNUSED_FUNCTION */
/*
* ini_str_lower() - Convert a string to lowercase
* return: statically allocated string
* s(in): String to convert
*
* Note: not re-entrant
*/
static char *
ini_str_lower (const char *s)
{
static char result[INI_BUFSIZ + 1];
int i;
if (s == NULL)
{
return NULL;
}
memset (result, 0, INI_BUFSIZ + 1);
i = 0;
while (s[i] && i < INI_BUFSIZ)
{
result[i] = (char) char_tolower ((int) s[i]);
i++;
}
result[INI_BUFSIZ] = (char) 0;
return result;
}
/*
* ini_str_trim() - Remove blanks at the beginning and the end of a string
* return: statically allocated string
* s(in): String to strip
*
* Note: not re-entrant
*/
static char *
ini_str_trim (char *s)
{
static char result[INI_BUFSIZ + 1];
char *last;
if (s == NULL)
{
return NULL;
}
while (char_isspace ((int) *s) && *s)
{
s++;
}
memset (result, 0, INI_BUFSIZ + 1);
strcpy (result, s);
last = result + strlen (result);
while (last > result)
{
if (!char_isspace ((int) *(last - 1)))
{
break;
}
last--;
}
*last = (char) 0;
return result;
}
/*
* ini_parse_line() - Load a single line from an INI file
* return: line status
* input_line(in): input line
* section(out): section name
* key(out): key name
* value(out): value
*
* Note:
*/
static INI_LINE_STATUS
ini_parse_line (char *input_line, char *section, char *key, char *value)
{
INI_LINE_STATUS status;
char line[INI_BUFSIZ + 1];
int len;
strcpy (line, ini_str_trim (input_line));
len = (int) strlen (line);
status = LINE_UNPROCESSED;
if (len < 1)
{
/* Empty line */
status = LINE_EMPTY;
}
else if (line[0] == '#')
{
/* Comment line */
status = LINE_COMMENT;
}
else if (line[0] == '[' && line[len - 1] == ']')
{
/* Section name */
char leading_char;
sscanf (line, "[%[^]]", section);
strcpy (section, ini_str_trim (section));
leading_char = section[0];
if (leading_char == '@' || leading_char == '%')
{
if (snprintf (section, INI_BUFSIZ + 1, "%c%s", leading_char, ini_str_trim (section + 1)) > INI_BUFSIZ)
{
assert_release (false);
section[INI_BUFSIZ] = '\0';
}
}
if (leading_char != '@')
{
strcpy (section, ini_str_lower (section));
}
status = LINE_SECTION;
}
else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
|| sscanf (line, "%[^=] = %[^;#]", key, value) == 2)
{
/* Usual key=value, with or without comments */
strcpy (key, ini_str_trim (key));
strcpy (key, ini_str_lower (key));
strcpy (value, ini_str_trim (value));
/*
* sscanf cannot handle '' or "" as empty values
* this is done here
*/
if (!strcmp (value, "\"\"") || (!strcmp (value, "''")))
{
value[0] = 0;
}
status = LINE_VALUE;
}
else if (sscanf (line, "%[^=] = %[;#]", key, value) == 2 || sscanf (line, "%[^=] %[=]", key, value) == 2)
{
/*
* Special cases:
* key=
* key=;
* key=#
*/
strcpy (key, ini_str_trim (key));
strcpy (key, ini_str_lower (key));
value[0] = 0;
status = LINE_VALUE;
}
else
{
/* Generate syntax error */
status = LINE_ERROR;
}
return status;
}
/*
* ini_parser_load() - Parse an ini file
* return: Pointer to newly allocated INI_TABLE
* ininame(in): ini file to read
*
* Note: The returned INI_TABLE must be freed using ini_parser_free()
*/
INI_TABLE *
ini_parser_load (const char *ininame)
{
FILE *in;
char line[INI_BUFSIZ + 1];
char section[INI_BUFSIZ + 1];
char key[INI_BUFSIZ + 1];
char tmp[(INI_BUFSIZ + 1) * 2];
char val[INI_BUFSIZ + 1];
int last = 0;
int len;
int lineno = 0;
int errs = 0;
INI_TABLE *ini;
if ((in = fopen (ininame, "r")) == NULL)
{
fprintf (stderr, "ini_parser: cannot open %s\n", ininame);
return NULL;
}
ini = ini_table_new (0);
if (!ini)
{
fclose (in);
return NULL;
}
memset (line, 0, sizeof (line));
memset (section, 0, sizeof (section));
memset (key, 0, sizeof (key));
memset (val, 0, sizeof (val));
last = 0;
while (fgets (line + last, INI_BUFSIZ - last, in) != NULL)
{
lineno++;
len = (int) strlen (line) - 1;
/* Safety check against buffer overflows */
if (line[len] != '\n' && len >= INI_BUFSIZ - 2)
{
fprintf (stderr, "ini_parser: input line too long in %s (%d)\n", ininame, lineno);
ini_table_free (ini);
fclose (in);
return NULL;
}
/* Get rid of \n and spaces at end of line */
while ((len > 0) && ((line[len] == '\n') || (char_isspace (line[len]))))
{
line[len] = 0;
len--;
}
/* Detect multi-line */
if (line[len] == '\\')
{
/* Multi-line value */
last = len;
continue;
}
else
{
last = 0;
}
switch (ini_parse_line (line, section, key, val))
{
case LINE_EMPTY:
case LINE_COMMENT:
break;
case LINE_SECTION:
errs = ini_table_set (ini, section, NULL, lineno);
break;
case LINE_VALUE:
sprintf (tmp, "%s:%s", section, key);
errs = ini_table_set (ini, tmp, val, lineno);
break;
case LINE_ERROR:
fprintf (stderr, "ini_parser: syntax error in %s (%d):\n", ininame, lineno);
fprintf (stderr, "-> %s\n", line);
errs++;
break;
default:
break;
}
memset (line, 0, sizeof (line));
last = 0;
if (errs < 0)
{
fprintf (stderr, "ini_parser: memory allocation failure\n");
break;
}
}
if (errs)
{
ini_table_free (ini);
ini = NULL;
}
fclose (in);
return ini;
}
/*
* ini_parser_free() - Free all memory associated to an ini INI_TABLE
* return: void
* ini(in): Dictionary to free
*
* Note:
*/
void
ini_parser_free (INI_TABLE * ini)
{
ini_table_free (ini);
}
/*
* ini_findsec() - Find name for section n in a INI_TABLE
* return: true or false
* ini(in): INI_TABLE to examine
* sec(in): section name to search
*
* Note:
*/
int
ini_findsec (INI_TABLE * ini, const char *sec)
{
int i;
if (ini == NULL || sec == NULL)
{
return 0;
}
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
continue;
}
if (strcasecmp (ini->key[i], sec) == 0)
{
return 1;
}
}
return 0;
}
/*
* ini_getsecname() - Get name for section n in a INI_TABLE
* return: name of section or NULL if fail
* ini(in): INI_TABLE to examine
*
* Note:
*/
char *
ini_getsecname (INI_TABLE * ini, int n, int *lineno)
{
int i, foundsec;
if (ini == NULL || n < 0)
{
return NULL;
}
foundsec = 0;
for (i = 0; i < ini->size; i++)
{
if (ini->key[i] == NULL)
{
continue;
}
if (strchr (ini->key[i], ':') == NULL)
{
foundsec++;
if (foundsec > n)
{
break;
}
}
}
if (foundsec <= n)
{
return NULL;
}
if (lineno != NULL && ini->lineno != NULL)
{
*lineno = ini->lineno[i];
}
return ini->key[i];
}
/*
* ini_hassec() - Check key string has section
* return: 1 true, or 0 false
* key(in): key to examine
*
* Note:
*/
int
ini_hassec (const char *key)
{
return (key[0] != ':');
}
/*
* ini_seccmp() - compare two key has same section
* return: 0 if not equal, or return section length
* key1(in): key to examine
* key2(in): key to examine
*
* Note:
*/
int
ini_seccmp (const char *key1, const char *key2, bool ignore_case)
{
const char *s1 = strchr (key1, ':');
const char *s2 = strchr (key2, ':');
int key1_sec_len, key2_sec_len;
if (s1)
{
key1_sec_len = CAST_STRLEN (s1 - key1);
}
else
{
key1_sec_len = (int) strlen (key1);
}
if (s2)
{
key2_sec_len = CAST_STRLEN (s2 - key2);
}
else
{
key2_sec_len = (int) strlen (key2);
}
if (key1_sec_len != key2_sec_len)
{
return 0;
}
if (ignore_case && strncasecmp (key1, key2, key1_sec_len) == 0)
{
return key1_sec_len;
}
if (ignore_case == false && strncmp (key1, key2, key1_sec_len) == 0)
{
return key1_sec_len;
}
return 0;
}
/*
* ini_get_internal() - Get the string associated to a key
* return: pointer to statically allocated character string
* ini(in): INI_TABLE to search
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note: 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
* do not free or modify returned pointer
*/
static const char *
ini_get_internal (INI_TABLE * ini, const char *key, const char *def, int *lineno)
{
char *lc_key;
const char *sval;
if (ini == NULL || key == NULL)
{
return def;
}
lc_key = ini_str_lower (key);
sval = ini_table_get (ini, lc_key, def, lineno);
return sval;
}
/*
* ini_getstr() - Get the string associated to a key
* return: pointer to statically allocated character string
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note: A key as read from an ini file is given as "key"
* If the key cannot be found, the pointer passed as 'def' is returned
* do not free or modify returned pointer
*/
const char *
ini_getstr (INI_TABLE * ini, const char *sec, const char *key, const char *def, int *lineno)
{
char sec_key[INI_BUFSIZ + 1];
sprintf (sec_key, "%s:%s", sec, key);
return ini_get_internal (ini, sec_key, def, lineno);
}
/*
* ini_getint() - Get the string associated to a key, convert to an int
* return: int
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note:
*/
int
ini_getint (INI_TABLE * ini, const char *sec, const char *key, int def, int *lineno)
{
const char *str;
int val;
str = ini_getstr (ini, sec, key, INI_INVALID_KEY, lineno);
if (str == INI_INVALID_KEY || str == NULL)
{
return def;
}
parse_int (&val, str, 0);
return val;
}
/*
* ini_getuint() - Get the string associated to a key, convert to positive int
* return: positive int
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note:
*/
int
ini_getuint (INI_TABLE * ini, const char *sec, const char *key, int def, int *lineno)
{
int result;
result = ini_getint (ini, sec, key, def, lineno);
if (result <= 0)
{
return def;
}
return result;
}
#if defined (ENABLE_UNUSED_FUNCTION)
/*
* ini_getuint_min() - Get the string associated to a key, convert to
* positive int with minimum limit
* return: positive int
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* min(in): minimum limit
* lineno(out): line number
*
* Note:
*/
int
ini_getuint_min (INI_TABLE * ini, const char *sec, const char *key, int def, int min, int *lineno)
{
int result;
result = ini_getuint (ini, sec, key, def, lineno);
if (result < min)
{
return min;
}
return result;
}
#endif /* ENABLE_UNUSED_FUNCTION */
/*
* ini_getuint_max() - Get the string associated to a key, convert to
* positive int with maximum limit
* return: positive int
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* max(in): maximum limit
* lineno(out): line number
*
* Note:
*/
int
ini_getuint_max (INI_TABLE * ini, const char *sec, const char *key, int def, int max, int *lineno)
{
int result;
result = ini_getuint (ini, sec, key, def, lineno);
if (result > max)
{
return max;
}
return result;
}
/*
* ini_gethex() - Get the string associated to a key, convert to an hex
* return: int
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note: the conversion may overflow. see strtol().
*/
int
ini_gethex (INI_TABLE * ini, const char *sec, const char *key, int def, int *lineno)
{
const char *str;
int val;
str = ini_getstr (ini, sec, key, INI_INVALID_KEY, lineno);
if (str == INI_INVALID_KEY || str == NULL)
{
return def;
}
parse_int (&val, str, 16);
return val;
}
/*
* ini_getfloat() - Get the string associated to a key, convert to an float
* return: float
* ini(in): INI_TABLE to search
* sec(in): section to look for
* key(in): key to look for
* def(in): default value to return if key not found
* lineno(out): line number
*
* Note: the conversion may overflow. see strtod().
*/
float
ini_getfloat (INI_TABLE * ini, const char *sec, const char *key, float def, int *lineno)
{
const char *str;
str = ini_getstr (ini, sec, key, INI_INVALID_KEY, lineno);
if (str == INI_INVALID_KEY || str == NULL)
{
return def;
}
return (float) strtod (str, NULL);
}