From aa4d426b4d3527d7e166df1a05058c9a4a0f6683 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Fri, 30 Apr 2021 00:33:56 +0200 Subject: initial/final commit --- iniparser-4.1/src/iniparser.c | 938 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 iniparser-4.1/src/iniparser.c (limited to 'iniparser-4.1/src/iniparser.c') diff --git a/iniparser-4.1/src/iniparser.c b/iniparser-4.1/src/iniparser.c new file mode 100644 index 0000000..d3be382 --- /dev/null +++ b/iniparser-4.1/src/iniparser.c @@ -0,0 +1,938 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.c + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ +/*---------------------------- Includes ------------------------------------*/ +#include +#include +#include +#include "iniparser.h" + +ssize_t _getline(char **lineptr, size_t *n, FILE *stream, + int (*fgetc_impl)(FILE*)); + +/*---------------------------- Defines -------------------------------------*/ +#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_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 Convert a string to lowercase in-place. + @param in String to convert. + @return value of in argument. + + This function convert a string into lowercase. + */ +/*--------------------------------------------------------------------------*/ +static char *strlwc_in_place(char *in) +{ + unsigned i ; + + if (in == NULL) + return NULL; + + i = 0; + while (in[i] != '\0') { + in[i] = tolower(in[i]); + i++; + } + + return in; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Find substrings without blanks at the beginning nor end. + @param s String to parse. + @param s_end End of string to parse. + @param begin Place to store pointer to the beginning of substring or NULL. + @param end Place to store pointer to the end of substring or NULL. + @return unsigned New size of the string. + */ +/*--------------------------------------------------------------------------*/ +static size_t strstrip(char *s, char *s_end, + char **begin, char **end) +{ + char *_begin, *_end; + + _begin = s; + _end = s_end; + + while (_begin < _end && isspace((int) *_begin)) + _begin++; + + while (_begin < _end && isspace((int) *(_end - 1))) + _end--; + + if (begin) + *begin = _begin; + if (end) + *end = _end; + return _end - _begin; +} + +/** + * This variable defines, whether multiline values should be parsed. + */ +static int process_multiline = 1; + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure whether to parse multiline values. + @param process_multiline Boolean (0 or 1) value. + + By default, multiline values will be parsed. If someone puts into their .ini + file 2 lines like those: + + outputdir=c:\temp\ + save_subject=1 + + then we're screwed. All will be treated as if it was on one line. + This function makes it possible to disable this linebreak feature. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_set_process_multiline(int process) +{ + process_multiline = process; +} + +/** + * This variable points to the function, that should be used for reading data. + */ +static int (*fgetc_impl)(FILE*) = fgetc; + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to read file. + @param impl Function to call. + + By default, fgetc() will be used for reading files. If a null pointer is passed + as impl the reading callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_set_fgetc_impl(int (*impl)(FILE*)) +{ + if (impl) + fgetc_impl = impl; + else + fgetc_impl = fgetc; +} + +/*-------------------------------------------------------------------------*/ +/** + @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 ; iuser_data, len); + d->user_data[len - 1] = ':'; + + for (j = 0 ; j < (size_t) d->size ; j++) { + if (d->key[j]==NULL) + continue; + if (!strncmp(d->key[j], d->user_data, len)) { + fprintf(f, + "%-30s = %s\n", + d->key[j]+len, + d->val[j] ? d->val[j] : ""); + } + } + + fprintf(f, "\n"); +} + +/*-------------------------------------------------------------------------*/ +/** + @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) +{ + size_t len, nkeys; + size_t j; + + nkeys = 0; + + if (d == NULL) + return nkeys; + if (! iniparser_find_entry(d, s)) + return nkeys; + + len = strlen(s) + 1; + if (len + 1 > d->user_data_len) + return nkeys; + + strlwc(s, d->user_data, len); + d->user_data[len - 1] = ':'; + + for (j = 0 ; j < (size_t) d->size; j++) { + if (d->key[j] == NULL) + continue; + if (!strncmp(d->key[j], d->user_data, len)) + 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) +{ + size_t i, j, len; + + if (d == NULL || keys == NULL) + return NULL; + if (! iniparser_find_entry(d, s)) + return NULL; + + len = strlen(s) + 1; + strlwc(s, d->user_data, len); + d->user_data[len - 1] = ':'; + + i = 0; + + for (j = 0; j < (size_t) d->size; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], d->user_data, len)) { + 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 * sval; + size_t len = strlen(key) + 1; + + if (d==NULL || key==NULL) + return def; + + if (len > d->user_data_len) + return def; + + strlwc(key, d->user_data, len); + sval = dictionary_get(d, d->user_data, 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) +{ + size_t len = strlen(entry) + 1; + int result; + + if (prepare_user_data_buf(ini, len)) + return -1; + + result = dictionary_set(ini, strlwc(entry, ini->user_data, len), val); + + return result; +} + +/*-------------------------------------------------------------------------*/ +/** + @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) +{ + size_t len = strlen(entry) + 1; + if (len <= ini->user_data_len) + dictionary_unset(ini, strlwc(entry, ini->user_data, len)); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Load a single line from an INI file + @param input_line Input line, may be concatenated multi-line input + @param section Output place to store section + @param key Output place to store key + @param value Output place to store value + @return line_status value + */ +/*--------------------------------------------------------------------------*/ +static line_status iniparser_line( + char * input_line, + char ** section, + char ** key, + char ** value) +{ + char *line, *line_end, *substr, *substr_end, *tmp; + + strstrip(input_line, input_line + strlen(input_line), &line, &line_end); + + if (*line == ';' || *line == '#') { + /* Comment line */ + return LINE_COMMENT; + } + if (line == line_end) { + return LINE_EMPTY; + } + if (line[0] == '[' && line_end[-1] == ']') { + /* Section name */ + strstrip(line + 1, line_end - 1, &substr, &substr_end); + *substr_end = '\0'; + *section = strlwc_in_place(substr); + return LINE_SECTION; + } + + /* Key and value */ + substr = line; + substr_end = substr; + while (*substr_end != '=') { + if (++substr_end == line_end) + break; + } + if (substr_end == line_end) + return LINE_ERROR; + tmp = substr_end; + + /* Get key */ + strstrip(substr, substr_end, &substr, &substr_end); + *substr_end = '\0'; + *key = strlwc_in_place(substr); + + /* Get value */ + substr = tmp + 1; + substr_end = line_end; + strstrip(substr, substr_end, &substr, &substr_end); + *substr_end = '\0'; + + if ((substr[0] == '\'' && (tmp = strchr(substr + 1, '\''))) || + (substr[0] == '"' && (tmp = strchr(substr + 1, '"')))) { + /* Handle quoted values */ + substr++; + substr_end = tmp; + } else { + /* Handle comments */ + for (tmp = substr; *tmp; tmp++) { + if (*tmp == ';' || *tmp == '#') + break; + } + if (*tmp) + strstrip(substr, tmp, &substr, &substr_end); + } + + *substr_end = '\0'; + *value = substr; + + return LINE_VALUE; +} + +static char empty_string[] = ""; + +/*-------------------------------------------------------------------------*/ +/** + @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 = NULL; + + char *read_buf = NULL; + size_t read_buf_len = 0; + ssize_t read_len; + + char *line = NULL, *tmp; + size_t line_len = 0; + int append_to_line = 0; + + char *section; + char *key; + char *val; + + const char *current_section = empty_string; + + int lineno = 0; + int errs = 0; + int sections = 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) + goto fail; + + while (1) { + errno = 0; + read_len = _getline(&read_buf, &read_buf_len, in, fgetc_impl); + if (read_len < 1) { + if (errno) + goto fail; + break; + } + /* printf("read buf: %s", read_buf); */ + if (read_buf[read_len - 1] == '\n') + read_buf[--read_len] = '\0'; + + lineno++; + + if (append_to_line) { + if (read_len > 0) { + tmp = realloc(line, line_len + read_len + 1); + if (!tmp) + goto fail; + line = tmp; + memcpy(line + line_len, read_buf, read_len + 1); + line_len += read_len; + } + } else { + line = malloc(read_len + 1); + if (!line) + goto fail; + memcpy(line, read_buf, read_len + 1); + line_len = read_len; + } + + /* Detect multi-line */ + append_to_line = 0; + if (process_multiline) { + tmp = strrchr(line, '\\'); + if (tmp && !strstrip(tmp + 1, line + line_len, NULL, NULL)) { + /* Multi-line value */ + line_len = tmp - line; + line[line_len] = '\0'; + append_to_line = 1; + continue; + } + } + + switch (iniparser_line(line, §ion, &key, &val)) { + case LINE_EMPTY: + case LINE_COMMENT: + break; + + case LINE_SECTION: + if (prepare_user_data_buf(dict, strlen(current_section) + 2)) + goto fail; + + if (dictionary_set(dict, section, NULL) < 0) + goto fail; + current_section = iniparser_getsecname(dict, sections++); + break; + + case LINE_VALUE: + if (prepare_user_data_buf + (dict, strlen(current_section) + strlen(key) + 2)) + goto fail; + + sprintf(dict->user_data, "%s:%s", current_section, key); + + if (dictionary_set(dict, dict->user_data, val)) + goto fail; + break; + + case LINE_ERROR: + iniparser_error_callback( + "iniparser: syntax error in %s (%d):\n-> %s\n", + ininame, + lineno, + line); + errs++; + break; + + default: + break; + } + + free(line); + } + if (errs) { + dictionary_del(dict); + dict = NULL; + } + free(read_buf); + fclose(in); + return dict; + +fail: + if (in) + fclose(in); + dictionary_del(dict); + free(read_buf); + free(line); + iniparser_error_callback("iniparser: memory allocation failure\n"); + return NULL; +} + +/*-------------------------------------------------------------------------*/ +/** + @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); +} -- cgit v1.2.3