From 1e137bae0f30ff57636a2c9489992050e5a9515a Mon Sep 17 00:00:00 2001 From: Dmitri Pal Date: Mon, 2 Aug 2010 21:44:15 -0400 Subject: [INI] Introducing configuration file object The patch contains two major pieces of work: * The beginning of the new INI interface significantly re-thought and reworked. * The implementation of the file object. Parsing was adjusted to use this object. --- common/ini/Makefile.am | 1 + common/ini/ini_config_priv.h | 40 +- common/ini/ini_configobj.c | 9 +- common/ini/ini_configobj.h | 920 ++++++++++++++++++++++++++++++++++++++++++- common/ini/ini_defines.h | 29 +- common/ini/ini_fileobj.c | 271 +++++++++++++ common/ini/ini_parse.c | 87 ++-- common/ini/ini_parse_ut.c | 44 ++- common/ini/ini_print.c | 22 ++ common/ini/ini_serialize.c | 2 +- 10 files changed, 1325 insertions(+), 100 deletions(-) create mode 100644 common/ini/ini_fileobj.c diff --git a/common/ini/Makefile.am b/common/ini/Makefile.am index 4e900f9..710367d 100644 --- a/common/ini/Makefile.am +++ b/common/ini/Makefile.am @@ -51,6 +51,7 @@ libini_config_la_SOURCES = \ ini_valueobj.c \ ini_valueobj.h \ ini_serialize.c \ + ini_fileobj.c \ ini_configobj.c \ ini_configobj.h \ ini_config_priv.h diff --git a/common/ini/ini_config_priv.h b/common/ini/ini_config_priv.h index 9dff6cd..9185e14 100644 --- a/common/ini/ini_config_priv.h +++ b/common/ini/ini_config_priv.h @@ -25,15 +25,51 @@ #include "collection.h" /* Configuration object */ -struct configobj { +struct ini_cfgobj { /* For now just a collection */ struct collection_item *cfg; - /* Boundary ? */ + /* Boundary */ + uint32_t boundary; /*... */ /* Statistics? Timestamps? When created? Modified? - TBD */ /*... */ }; + +/* Configuration file object */ +struct ini_cfgfile { + /***********************************/ + /* Externally controlled variables */ + /***********************************/ + /* File name for the configuration file */ + char *filename; + /* File stream */ + FILE *file; + /* File descriptor that is passed in */ + int fd; + /* Error level */ + int error_level; + /* Collision flags - define how to merge things */ + uint32_t collision_flags; + /* Collision flags - define how to merge things */ + uint32_t metadata_flags; + /**********************/ + /* Internal variables */ + /**********************/ + /* Collection of errors detected during parsing */ + struct collection_item *error_list; + /* Metadata about the file */ + struct collection_item *metadata; + /* Count of error lines */ + unsigned count; +}; + +/* Parsing error */ +struct ini_parse_error { + unsigned line; + int error; +}; + /* Internal cleanup callback */ void ini_cleanup_cb(const char *property, int property_len, diff --git a/common/ini/ini_configobj.c b/common/ini/ini_configobj.c index e75f093..4da9a7c 100644 --- a/common/ini/ini_configobj.c +++ b/common/ini/ini_configobj.c @@ -57,7 +57,7 @@ void ini_cleanup_cb(const char *property, } /* Traverse the collection and clean the object */ -void ini_config_destroy(struct configobj *ini_config) +void ini_config_destroy(struct ini_cfgobj *ini_config) { TRACE_FLOW_ENTRY(); @@ -75,10 +75,10 @@ void ini_config_destroy(struct configobj *ini_config) } /* Create a config object */ -int ini_config_create(struct configobj **ini_config) +int ini_config_create(struct ini_cfgobj **ini_config) { int error = EOK; - struct configobj *new_co = NULL; + struct ini_cfgobj *new_co = NULL; TRACE_FLOW_ENTRY(); @@ -88,7 +88,7 @@ int ini_config_create(struct configobj **ini_config) } errno = 0; - new_co = malloc(sizeof(struct configobj)); + new_co = malloc(sizeof(struct ini_cfgobj)); if (!new_co) { error = errno; TRACE_ERROR_NUMBER("Failed to allocate memory", ENOMEM); @@ -96,6 +96,7 @@ int ini_config_create(struct configobj **ini_config) } new_co->cfg = NULL; + new_co->boundary = INI_WRAP_BOUNDARY; /* Create a collection to hold configuration data */ error = col_create_collection(&(new_co->cfg), diff --git a/common/ini/ini_configobj.h b/common/ini/ini_configobj.h index 4b1b31f..8ecb148 100644 --- a/common/ini/ini_configobj.h +++ b/common/ini/ini_configobj.h @@ -22,33 +22,933 @@ #ifndef INI_CONFIGOBJ_H #define INI_CONFIGOBJ_H +#include +#include +#include +#include #include #include "simplebuffer.h" +/** + * @defgroup errorlevel Error tolerance constants + * + * Constants in this section define what to do if + * error or warning encountered while parsing the INI file. + * + * @{ + */ +/** @brief Fail if any problem is detected. */ +#define INI_STOP_ON_ANY 0 +/** @brief Best effort - do not fail. */ +#define INI_STOP_ON_NONE 1 +/** @brief Fail on errors only. */ +#define INI_STOP_ON_ERROR 2 + +/** + * @} + */ + +/** + * @defgroup parseerr Parsing errors and warnings + * + * @{ + */ +/** @brief Line is too long (Error). */ +#define ERR_LONGDATA 1 +/** @brief No closing bracket in section definition (Error). */ +#define ERR_NOCLOSESEC 2 +/** @brief Section name is missing (Error). */ +#define ERR_NOSECTION 3 +/** @brief Section name too long (Error). */ +#define ERR_SECTIONLONG 4 +/** @brief No equal sign (Error). */ +#define ERR_NOEQUAL 5 +/** @brief No key before equal sign (Error). */ +#define ERR_NOKEY 6 +/** @brief Key is too long (Error). */ +#define ERR_LONGKEY 7 +/** @brief Failed to read line (Error). */ +#define ERR_READ 8 +/** @brief Line starts with space when it should not (Error). */ +#define ERR_SPACE 9 + +/** @brief Size of the error array. */ +#define ERR_MAXPARSE ERR_SPACE + +/** + * @} + */ + +/** + * @defgroup structures Structures + * @{ + */ + + +struct ini_cfgobj; +struct ini_cfgfile; + +/** @brief Structure that holds error number and + * line number for the encountered error. + */ +struct ini_parse_error; + + +/** + * @} + */ + + /********************************************************************/ /* THIS IS A BEGINNING OF THE THE NEW CONFIG OBJECT INTERFACE - TBD */ /* It will be moved to the ini_config.h when it is ready */ /********************************************************************/ -struct configobj; /* Create a configuration object */ -int ini_config_create(struct configobj **ini_config); +int ini_config_create(struct ini_cfgobj **ini_config); /* Destroy a configuration object */ -void ini_config_destroy(struct configobj *ini_config); +void ini_config_destroy(struct ini_cfgobj *ini_config); + +/* Create a file object for parsing a config file */ +int ini_config_file_open(const char *filename, + int error_level, + uint32_t collision_flags, + uint32_t metadata_flags, + struct ini_cfgfile **file_ctx); + +/* Close file context and destroy the object */ +void ini_config_file_close(struct ini_cfgfile *file_ctx); + +/* How many errors do we have in the list ? */ +unsigned ini_config_error_count(struct ini_cfgfile *file_ctx); + +/* Get the list of error strings */ +int ini_config_get_errors(struct ini_cfgfile *file_ctx, + char ***errors); + +/* Get the fully resolved file name */ +const char *ini_config_get_filename(struct ini_cfgfile *file_ctx); + +/* Free error strings */ +void ini_config_free_errors(char **errors); + +/* Parse the file and create a config object */ +int ini_config_parse(struct ini_cfgfile *file_ctx, + struct ini_cfgobj *ini_config); + +/* Function to print errors from the list */ +void ini_print_errors(FILE *file, char **error_list); + + +/* TBD rename the function */ +/** @brief Function to return a parsing error as a string. + * + * @param[in] parsing_error Error code for the parsing error. + * + * @return Error string. + */ +const char *parsing_error_str(int parsing_error); + + +/* Merge two configurations together creating a new one */ +int ini_config_merge(struct ini_cfgobj *first, + struct ini_cfgobj *second, + uint32_t collision_flags, + struct ini_cfgobj **result); + +/* Set the folding boundary for multiline values. + * Use before serializing and saving to a file if the + * default boundary of 80 characters does not work for you. + */ +int ini_config_set_wrap(struct ini_cfgobj *ini_config, + uint32_t boundary); /* Serialize configuration object into provided buffer */ -int ini_config_serialize(struct configobj *ini_config, +int ini_config_serialize(struct ini_cfgobj *ini_config, struct simplebuffer *sbobj); -int ini_config_parse(FILE *file, - const char *config_filename, - struct configobj *ini_config, - int error_level, - struct collection_item **error_list, - uint32_t boundary); +/* Check access */ +int ini_config_access_check(struct ini_cfgfile *file_ctx, + uint32_t flags, + uid_t uid, + gid_t gid, + mode_t mode, + mode_t mask); + +/* Determins if two file context different by comparing + * - time stamp + * - device ID + * - i-node + */ +int ini_config_changed(struct ini_cfgfile *file_ctx, + struct ini_cfgfile *file_ctx_saved, + int *changed); + + +/****************************************************/ +/* VALUE MANAGEMENT */ +/****************************************************/ + +/** + * @brief Get list of sections. + * + * Get list of sections from the configuration object + * as an array of strings. + * Function allocates memory for the array of the sections. + * Use \ref free_section_list() to free allocated memory. + * + * @param[in] ini_config Configuration object. + * @param[out] size If not NULL parameter will + * receive number of sections + * in the configuration. + * @param[out] error If not NULL parameter will + * receive the error code. + * 0 - Success. + * EINVAL - Invalid parameter. + * ENOMEM - No memory. + * + * @return Array of strings. + */ +char **get_section_list(struct ini_cfgobj *ini_config, + int *size, + int *error); + +/** + * @brief Free list of sections. + * + * The section array created by \ref get_section_list() + * should be freed using this function. + * + * @param[in] section_list Array of strings returned by + * \ref get_section_list() function. + */ +void free_section_list(char **section_list); + +/** + * @brief Get list of attributes. + * + * Get list of attributes in a section as an array of strings. + * Function allocates memory for the array of attributes. + * Use \ref free_attribute_list() to free allocated memory. + * + * @param[in] ini_config Configuration object. + * @param[in] section Section name. + * @param[out] size If not NULL parameter will + * receive number of attributes + * in the section. + * @param[out] error If not NULL parameter will + * receive the error code. + * 0 - Success. + * EINVAL - Invalid parameter. + * ENOMEM - No memory. + * + * @return Array of strings. + */ +char **get_attribute_list(struct ini_cfgobj *ini_config, + const char *section, + int *size, + int *error); + +/** + * @brief Free list of attributes. + * + * The attribute array created by \ref get_attribute_list() + * should be freed using this function. + * + * @param[in] attr_list Array of strings returned by + * \ref get_attribute_list() function. + */ +void free_attribute_list(char **attr_list); +/** + * @brief Get an integer value from the configuration. + * + * Function looks up the section and key in + * in the configuration object and tries to convert + * into an integer number. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * The value range is from INT_MIN to INT_MAX. + * + * @param[in] ini_config Configuration object. + * @param[in] section Section of the configuration file. + * @param[in] name Key to look up inside the section. + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] value Fetched value or default + * + * In case of failure the function assignes default value + * to the resulting value and returns corresponging error code. + * + * @return 0 - Success. + * @return EINVAL - Argument is invalid. + * @return EIO - Conversion failed due invalid characters. + * @return ERANGE - Value is out of range. + * @return ENOKEY - Value is not found. + * @return ENOMEM - No memory. + */ +int get_int_config_value(struct ini_cfgobj *ini_config, + const char *section, + const char *name, + int strict, + int def, + int *value); + +/* Similar functions are below */ +int get_long_config_value(struct ini_cfgobj *ini_config, + const char *section, + const char *name, + int strict, + long def, + long *value); + +int get_unsigned_config_value(struct ini_cfgobj *ini_config, + const char *section, + const char *name, + int strict, + unsigned def, + unsigned *value); + +int get_ulong_config_value(struct ini_cfgobj *ini_config, + const char *section, + const char *name, + int strict, + unsigned long def, + unsigned long *value); + + + + + + + +#ifdef THE_PART_I_HAVE_PROCESSED + + + + + + +/** + * @brief Convert item value to integer number. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an int32_t number. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * The value range is from INT_MIN to INT_MAX. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * - ERANGE - Value is out of range. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +int32_t get_int32_config_value(struct collection_item *item, + int strict, + int32_t def, + int *error); + +/** + * @brief Convert item value to integer number. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an uint32_t number. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * The value range is from 0 to ULONG_MAX. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * - ERANGE - Value is out of range. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +uint32_t get_uint32_config_value(struct collection_item *item, + int strict, + uint32_t def, + int *error); + +/** + * @brief Convert item value to integer number. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an int64_t number. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * The value range is from LLONG_MIN to LLONG_MAX. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * - ERANGE - Value is out of range. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +int64_t get_int64_config_value(struct collection_item *item, + int strict, + int64_t def, + int *error); + +/** + * @brief Convert item value to integer number. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an uint64_t number. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * The value range is from 0 to ULLONG_MAX. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * - ERANGE - Value is out of range. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +uint64_t get_uint64_config_value(struct collection_item *item, + int strict, + uint64_t def, + int *error); + +/** + * @brief Convert item value to floating point number. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into a floating point number. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * If "strict" parameter is non zero the function will fail + * if there are more characters after the last digit. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] strict Fail the function if + * the symbol after last digit + * is not valid. + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +double get_double_config_value(struct collection_item *item, + int strict, + double def, + int *error); + +/** + * @brief Convert item value into a logical value. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into a Boolean. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] def Default value to use if + * conversion failed. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * + * @return Converted value. + * In case of failure the function returns default value and + * sets error code into the provided variable. + */ +unsigned char get_bool_config_value(struct collection_item *item, + unsigned char def, + int *error); + +/** + * @brief Get string configuration value + * + * Function creates a copy of the string value stored in the item. + * Returned value needs to be freed after use. + * If error occurred the returned value will be NULL. + * + * @param[in] item Item to use. + * It must be retrieved using + * \ref get_config_item(). + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - ENOMEM - No memory. + * + * @return Copy of the string or NULL. + */ +char *get_string_config_value(struct collection_item *item, + int *error); +/** + * @brief Function returns the string stored in the item. + * + * Function returns a reference to the string value + * stored inside the item. This string can't be altered. + * The string will go out of scope if the item is deleted. + * + * @param[in] item Item to use. + * It must be retrieved using + * \ref get_config_item(). + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * + * @return String from the item. + */ +const char *get_const_string_config_value(struct collection_item *item, + int *error); + +/** + * @brief Convert item value into a binary sequence. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into a sequence of bytes. + * Any of the conversion functions + * can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * The function allocates memory. + * It is the responsibility of the caller to free it after use. + * Use \ref free_bin_config_value() for this purpose. + * Functions will return NULL if conversion failed. + * + * Function assumes that the value being interpreted + * has a special format. + * The string should be taken in single quotes + * and consist of hex encoded value represented by + * two hex digits per byte. + * Case does not matter. + * + * Example: '0a2BFeCc' + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[out] length Variable that optionally receives + * the length of the binary + * sequence. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed due + * invalid characters. + * - ENOMEM - No memory. + * + * @return Converted value. + * In case of failure the function returns NULL. + */ +char *get_bin_config_value(struct collection_item *item, + int *length, + int *error); + +/** + * @brief Free binary buffer + * + * Free binary value returned by \ref get_bin_config_value(). + * + * @param[in] bin Binary buffer to free. + * + */ +void free_bin_config_value(char *bin); + +/** + * @brief Convert value to an array of strings. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an array of strings. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * Separator string includes up to three different separators. + * If separator NULL, comma is assumed. + * The spaces are trimmed automatically around separators + * in the string. + * The function drops empty tokens from the list. + * This means that the string like this: "apple, ,banana, ,orange ," + * will be translated into the list of three items: + * "apple","banana" and "orange". + * + * The length of the allocated array is returned in "size". + * Size and error parameters can be NULL. + * Use \ref free_string_config_array() to free the array after use. + * + * The array is always NULL terminated so + * it is safe not to get size and just loop until + * array element is NULL. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] sep String cosisting of separator + * symbols. For example: ",.;" would mean + * that comma, dot and semicolon + * should be treated as separators + * in the value. + * @param[out] size Variable that optionally receives + * the size of the array. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed. + * - ENOMEM - No memory. + * + * @return Array of strings. + * In case of failure the function returns NULL. + */ +char **get_string_config_array(struct collection_item *item, + const char *sep, + int *size, + int *error); + +/** + * @brief Convert value to an array of strings. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an array of strings. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * Separator string includes up to three different separators. + * If separator NULL, comma is assumed. + * The spaces are trimmed automatically around separators + * in the string. + * The function does not drop empty tokens from the list. + * This means that the string like this: "apple, ,banana, ,orange ," + * will be translated into the list of five items: + * "apple", "", "banana", "" and "orange". + * + * The length of the allocated array is returned in "size". + * Size and error parameters can be NULL. + * Use \ref free_string_config_array() to free the array after use. + * + * The array is always NULL terminated so + * it is safe not to get size and just loop until + * array element is NULL. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[in] sep String cosisting of separator + * symbols. For example: ",.;" would mean + * that comma, dot and semicolon + * should be treated as separators + * in the value. + * @param[out] size Variable that optionally receives + * the size of the array. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed. + * - ENOMEM - No memory. + * + * @return Array of strings. + * In case of failure the function returns NULL. + */ +char **get_raw_string_config_array(struct collection_item *item, + const char *sep, + int *size, + int *error); + +/** + * @brief Convert value to an array of long values. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an array of long values. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * Separators inside the string are detected automatically. + * The spaces are trimmed automatically around separators + * in the string. + * + * The length of the allocated array is returned in "size". + * Size parameter can't be NULL. + * + * Use \ref free_long_config_array() to free the array after use. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[out] size Variable that receives + * the size of the array. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed. + * - ERANGE - Value is out of range. + * - ENOMEM - No memory. + * + * @return Array of long values. + * In case of failure the function returns NULL. + */ +long *get_long_config_array(struct collection_item *item, + int *size, + int *error); + +/** + * @brief Convert value to an array of floating point values. + * + * This is a conversion function. + * It converts the value read from the INI file + * and stored in the configuration item + * into an array of floating point values. Any of the conversion + * functions can be used to try to convert the value + * stored as a string inside the item. + * The results can be different depending upon + * how the caller tries to interpret the value. + * + * Separators inside the string are detected automatically. + * The spaces are trimmed automatically around separators + * in the string. + * + * The length of the allocated array is returned in "size". + * Size parameter can't be NULL. + * + * Use \ref free_double_config_array() to free the array after use. + * + * @param[in] item Item to interpret. + * It must be retrieved using + * \ref get_config_item(). + * @param[out] size Variable that receives + * the size of the array. + * @param[out] error Variable will get the value + * of the error code if + * error happened. + * Can be NULL. In this case + * function does not set + * the code. + * Codes: + * - 0 - Success. + * - EINVAL - Argument is invalid. + * - EIO - Conversion failed. + * - ENOMEM - No memory. + * + * @return Array of floating point values. + * In case of failure the function returns NULL. + */ +double *get_double_config_array(struct collection_item *item, + int *size, + int *error); + +/** + * @brief Free array of string values. + * + * Use this function to free the array returned by + * \ref get_string_config_array() or by + * \ref get_raw_string_config_array(). + * + * @param[in] str_config Array of string values. + */ +void free_string_config_array(char **str_config); + +/** + * @brief Free array of long values. + * + * Use this function to free the array returned by + * \ref get_long_config_array(). + * + * @param[in] array Array of long values. + */ +void free_long_config_array(long *array); +/** + * @brief Free array of floating pointer values. + * + * Use this function to free the array returned by + * \ref get_double_config_array(). + * + * @param[in] array Array of floating pointer values. + */ +void free_double_config_array(double *array); + +#endif #endif diff --git a/common/ini/ini_defines.h b/common/ini/ini_defines.h index efa9c98..bead998 100644 --- a/common/ini/ini_defines.h +++ b/common/ini/ini_defines.h @@ -46,8 +46,8 @@ #define FAILED_TO_PROC_V _("Internal Error. Failed to process list of validation errors.\n") #define ERROR_HEADER_V _("Validation errors and warnings in file: %s\n") -#define LINE_FORMAT _("%s (%d) on line %d: %s\n") - +#define LINE_FORMAT _("%s (%d) on line %d: %s") +#define MAX_ERROR_LINE 120 /* Codes that parsing function can return */ #define RET_PAIR 0 @@ -59,6 +59,7 @@ #define RET_ERROR 6 #define INI_ERROR "errors" +#define INI_METADATA "meta" #define INI_ERROR_NAME "errname" #define INI_CONFIG_NAME "INI" @@ -73,10 +74,26 @@ #define CONVERSION_BUFFER 80 /* Size of the block for a value */ -#define INI_VALUE_BLOCK 100 - -/* This constant belongs here. Move from ini_config - TBD */ -/* #define COL_CLASS_INI_BASE 20000 */ +#define INI_VALUE_BLOCK 100 + +/* Default boundary */ +#define INI_WRAP_BOUNDARY 80 + +/* This constant belongs here. */ +#define COL_CLASS_INI_BASE 20000 +/** + * @brief A one level collection of parse errors. + * + * Collection stores \ref parse_error structures. + */ +#define COL_CLASS_INI_PERROR COL_CLASS_INI_BASE + 2 + +/** + * @brief Collection of metadata. + * + * Collection that stores metadata. + */ +#define COL_CLASS_INI_META COL_CLASS_INI_BASE + 4 /* Different error string functions can be passed as callbacks */ diff --git a/common/ini/ini_fileobj.c b/common/ini/ini_fileobj.c new file mode 100644 index 0000000..117608c --- /dev/null +++ b/common/ini/ini_fileobj.c @@ -0,0 +1,271 @@ +/* + INI LIBRARY + + File context related functions + + Copyright (C) Dmitri Pal 2010 + + INI Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + INI Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with INI Library. If not, see . +*/ + +#define _GNU_SOURCE +#include +#include +#include +#include "config.h" +#include "trace.h" +#include "ini_defines.h" +#include "ini_configobj.h" +#include "ini_config_priv.h" +#include "collection.h" +#include "path_utils.h" +#include "collection_tools.h" + + +/* Close file context and destroy the object */ +void ini_config_file_close(struct ini_cfgfile *file_ctx) +{ + TRACE_FLOW_ENTRY(); + + if(file_ctx) { + free(file_ctx->filename); + col_destroy_collection(file_ctx->error_list); + col_destroy_collection(file_ctx->metadata); + fclose(file_ctx->file); + free(file_ctx); + } + + TRACE_FLOW_EXIT(); +} + +/* Create a file object for parsing a config file */ +int ini_config_file_open(const char *filename, + int error_level, + uint32_t collision_flags, + uint32_t metadata_flags, + struct ini_cfgfile **file_ctx) +{ + int error = EOK; + struct ini_cfgfile *new_ctx = NULL; + + TRACE_FLOW_ENTRY(); + + if ((!filename) || (!file_ctx)) { + TRACE_ERROR_NUMBER("Invalid parameter.", EINVAL); + return EINVAL; + } + + /* Allocate structure */ + errno = 0; + new_ctx = malloc(sizeof(struct ini_cfgfile)); + if (!new_ctx) { + error = errno; + TRACE_ERROR_NUMBER("Failed to allocate file ctx.", error); + return error; + } + + /* Construct the full file path */ + errno = 0; + new_ctx->filename = malloc(PATH_MAX + 1); + if (!(new_ctx->filename)) { + error = errno; + ini_config_file_close(new_ctx); + TRACE_ERROR_NUMBER("Failed to allocate memory for file path.", error); + return error; + } + + /* Construct path */ + error = make_normalized_absolute_path(new_ctx->filename, + PATH_MAX, + filename); + if(error) { + TRACE_ERROR_NUMBER("Failed to resolve path", error); + ini_config_file_close(new_ctx); + return error; + } + + /* Open file */ + TRACE_INFO_STRING("File", new_ctx->filename); + errno = 0; + new_ctx->file = NULL; + new_ctx->file = fopen(new_ctx->filename, "r"); + if (!(new_ctx->file)) { + error = errno; + TRACE_ERROR_NUMBER("Failed to open file", error); + ini_config_file_close(new_ctx); + return error; + } + + /* Store flags */ + new_ctx->error_level = error_level; + new_ctx->collision_flags = collision_flags; + new_ctx->metadata_flags = metadata_flags; + new_ctx->count = 0; + + /* Create internal collections */ + error = col_create_collection(&(new_ctx->error_list), + INI_ERROR, + COL_CLASS_INI_PERROR); + if (error) { + TRACE_ERROR_NUMBER("Failed to create error list", error); + ini_config_file_close(new_ctx); + return error; + } + + error = col_create_collection(&(new_ctx->metadata), + INI_METADATA, + COL_CLASS_INI_META); + if (error) { + TRACE_ERROR_NUMBER("Failed to create metadata collection", error); + ini_config_file_close(new_ctx); + return error; + } + + *file_ctx = new_ctx; + TRACE_FLOW_EXIT(); + return error; +} + + +/* How many errors do we have in the list ? */ +unsigned ini_config_error_count(struct ini_cfgfile *file_ctx) +{ + unsigned count = 0; + + TRACE_FLOW_ENTRY(); + + count = file_ctx->count; + + TRACE_FLOW_EXIT(); + return count; + +} + +/* Free error strings */ +void ini_config_free_errors(char **errors) +{ + TRACE_FLOW_ENTRY(); + + col_free_property_list(errors); + + TRACE_FLOW_EXIT(); +} + +/* Get the list of error strings */ +int ini_config_get_errors(struct ini_cfgfile *file_ctx, + char ***errors) +{ + char **errlist = NULL; + struct collection_iterator *iterator = NULL; + int error; + struct collection_item *item = NULL; + struct ini_parse_error *pe; + unsigned int count = 0; + char *line; + + TRACE_FLOW_ENTRY(); + + /* If we have something to print print it */ + if ((!errors) || (!file_ctx)) { + TRACE_ERROR_NUMBER("Invalid parameter.", EINVAL); + return EINVAL; + } + + + errno = 0; + errlist = calloc(file_ctx->count + 1, sizeof(char *)); + if (!errlist) { + error = errno; + TRACE_ERROR_NUMBER("Failed to allocate memory for errors.", error); + return error; + } + + /* Bind iterator */ + error = col_bind_iterator(&iterator, + file_ctx->error_list, + COL_TRAVERSE_DEFAULT); + if (error) { + TRACE_ERROR_NUMBER("Faile to bind iterator:", error); + ini_config_free_errors(errlist); + return error; + } + + while(1) { + /* Loop through a collection */ + error = col_iterate_collection(iterator, &item); + if (error) { + TRACE_ERROR_NUMBER("Error iterating collection", error); + col_unbind_iterator(iterator); + ini_config_free_errors(errlist); + return error; + } + + /* Are we done ? */ + if (item == NULL) break; + + /* Process collection header */ + if (col_get_item_type(item) == COL_TYPE_COLLECTION) { + continue; + } + else { + /* Put error into provided format */ + pe = (struct ini_parse_error *)(col_get_item_data(item)); + + /* Would be nice to have asprintf function... + * ...but for now we know that all the errors + * are pretty short and will fir into the predefined + * error length buffer. + */ + errno = 0; + line = malloc(MAX_ERROR_LINE + 1); + if (!line) { + error = errno; + TRACE_ERROR_NUMBER("Failed to get memory for error.", error); + col_unbind_iterator(iterator); + ini_config_free_errors(errlist); + return error; + } + + snprintf(line, MAX_ERROR_LINE, LINE_FORMAT, + col_get_item_property(item, NULL), + pe->error, + pe->line, + parsing_error_str(pe->error)); + + errlist[count] = line; + count++; + } + + } + + /* Do not forget to unbind iterator - otherwise there will be a leak */ + col_unbind_iterator(iterator); + + *errors = errlist; + + TRACE_FLOW_EXIT(); + return error; +} + +/* Get the fully resolved file name */ +const char *ini_config_get_filename(struct ini_cfgfile *file_ctx) +{ + const char *ret; + TRACE_FLOW_ENTRY(); + + ret = file_ctx->filename; + + TRACE_FLOW_EXIT(); + return ret; +} diff --git a/common/ini/ini_parse.c b/common/ini/ini_parse.c index 843acf2..253b78b 100644 --- a/common/ini/ini_parse.c +++ b/common/ini/ini_parse.c @@ -42,7 +42,6 @@ struct parser_obj { FILE *file; struct collection_item *top; struct collection_item *el; - struct collection_item **el_acceptor; const char *filename; int error_level; /* Wrapping boundary */ @@ -109,7 +108,7 @@ int parser_create(FILE *file, const char *config_filename, struct collection_item *ini_config, int error_level, - struct collection_item **error_list, + struct collection_item *error_list, uint32_t boundary, struct parser_obj **po) { @@ -144,7 +143,7 @@ int parser_create(FILE *file, /* Save external data */ new_po->file = file; new_po->top = ini_config; - new_po->el_acceptor = error_list; + new_po->el = error_list; new_po->filename = config_filename; new_po->error_level = error_level; new_po->boundary = boundary; @@ -161,7 +160,6 @@ int parser_create(FILE *file, new_po->raw_lines = NULL; new_po->raw_lengths = NULL; new_po->ret = EOK; - new_po->el = NULL; /* Create a queue */ new_po->queue = NULL; @@ -827,46 +825,20 @@ static int parser_error(struct parser_obj *po) uint32_t action; int idx = 0; const char *errtxt[] = { ERROR_TXT, WARNING_TXT }; - struct parse_error pe; + struct ini_parse_error pe; TRACE_FLOW_ENTRY(); - /* Create collection for errors */ - if ((po->el_acceptor) && (!(po->el))) { - error = col_create_collection(&(po->el), INI_ERROR, COL_CLASS_INI_PERROR); - if (error) { - TRACE_ERROR_NUMBER("Failed to create error collection", error); - return error; - } - } - - /* Try to add to the error list only if it is present */ - if (po->el) { - - /* If this is the first error add file name as the first item */ - if (po->ret == EOK) { - error = col_add_str_property(po->el, - NULL, - INI_ERROR_NAME, - po->filename, - 0); - if (error) { - TRACE_ERROR_NUMBER("Failed to and name to collection", error); - return error; - } - } - - pe.line = po->linenum; - /* Clear the warning bit */ - pe.error = po->last_error & ~INI_WARNING; - if (po->last_error & INI_WARNING) idx = 1; - error = col_add_binary_property(po->el, NULL, - errtxt[idx], &pe, sizeof(pe)); - if (error) { - TRACE_ERROR_NUMBER("Failed to add error to collection", - error); - return error; - } + pe.line = po->linenum; + /* Clear the warning bit */ + pe.error = po->last_error & ~INI_WARNING; + if (po->last_error & INI_WARNING) idx = 1; + error = col_add_binary_property(po->el, NULL, + errtxt[idx], &pe, sizeof(pe)); + if (error) { + TRACE_ERROR_NUMBER("Failed to add error to collection", + error); + return error; } /* Exit if there was an error parsing file */ @@ -963,10 +935,6 @@ int parser_run(struct parser_obj *po) if (action == PARSE_DONE) { TRACE_INFO_NUMBER("We are done", error); error = po->ret; - if ((po->el_acceptor) && (po->el)) { - *(po->el_acceptor) = po->el; - po->el = NULL; - } break; } @@ -983,12 +951,8 @@ int parser_run(struct parser_obj *po) } /* Top level wrapper around the parser */ -int ini_config_parse(FILE *file, - const char *config_filename, - struct configobj *ini_config, - int error_level, - struct collection_item **error_list, - uint32_t boundary) +int ini_config_parse(struct ini_cfgfile *file_ctx, + struct ini_cfgobj *ini_config) { int error = EOK; struct parser_obj *po; @@ -1000,12 +964,12 @@ int ini_config_parse(FILE *file, return EINVAL; } - error = parser_create(file, - config_filename, + error = parser_create(file_ctx->file, + file_ctx->filename, ini_config->cfg, - error_level, - error_list, - boundary, + file_ctx->error_level, + file_ctx->error_list, + ini_config->boundary, &po); if (error) { TRACE_ERROR_NUMBER("Failed to perform an action", error); @@ -1013,11 +977,18 @@ int ini_config_parse(FILE *file, } error = parser_run(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to parse file", error); + col_get_collection_count(file_ctx->error_list, &(file_ctx->count)); + if(file_ctx->count) (file_ctx->count)--; + parser_destroy(po); + return error; + } parser_destroy(po); - TRACE_INFO_NUMBER("Parsing returned:", error); + + TRACE_INFO_NUMBER("Count returned:", error); TRACE_FLOW_EXIT(); return error; - } diff --git a/common/ini/ini_parse_ut.c b/common/ini/ini_parse_ut.c index af67e06..36b7a33 100644 --- a/common/ini/ini_parse_ut.c +++ b/common/ini/ini_parse_ut.c @@ -24,7 +24,6 @@ #include #include #include "ini_defines.h" -#include "ini_config.h" #include "ini_configobj.h" #include "simplebuffer.h" #include "config.h" @@ -43,44 +42,51 @@ typedef int (*test_fn)(void); int test_one_file(const char *filename) { int error = EOK; + struct ini_cfgfile *file_ctx = NULL; FILE *ff = NULL; char new_file[100]; - struct configobj *ini_config = NULL; - struct collection_item *error_list = NULL; + struct ini_cfgobj *ini_config = NULL; + char **error_list = NULL; struct simplebuffer *sbobj = NULL; uint32_t left = 0; + unsigned count = 0; INIOUT(printf("<==== Testing file %s ====>\n", filename)); /* Create config collection */ error = ini_config_create(&ini_config); - if (error != EOK) { + if (error) { printf("Failed to create collection. Error %d.\n", error); return error; } - errno = 0; - ff = fopen(filename,"r"); - if(!ff) { - error = errno; + error = ini_config_file_open(filename, + INI_STOP_ON_NONE, + 0, /* TBD */ + 0, /* TBD */ + &file_ctx); + if (error) { printf("Failed to open file. Error %d.\n", error); ini_config_destroy(ini_config); return error; } - error = ini_config_parse(ff, - filename, - ini_config, - INI_STOP_ON_NONE, - &error_list, - 80); - fclose(ff); - if (error != EOK) { + error = ini_config_parse(file_ctx, + ini_config); + if (error) { INIOUT(printf("Failed to parse configuration. Error %d.\n", error)); - INIOUT(print_file_parsing_errors(stdout, error_list)); - col_destroy_collection(error_list); + + if (ini_config_error_count(file_ctx)) { + INIOUT(printf("Errors detected while parsing: %s\n", + ini_config_get_filename(file_ctx))); + ini_config_get_errors(file_ctx, &error_list); + INIOUT(ini_print_errors(stdout, error_list)); + } + /* We do not return here intentionally */ } + ini_config_file_close(file_ctx); + error = simplebuffer_alloc(&sbobj); if (error) { TRACE_ERROR_NUMBER("Failed to allocate dynamic string.", error); @@ -90,7 +96,7 @@ int test_one_file(const char *filename) error = ini_config_serialize(ini_config, sbobj); if (error != EOK) { - printf("Failed to parse configuration. Error %d.\n", error); + printf("Failed to serialize configuration. Error %d.\n", error); ini_config_destroy(ini_config); simplebuffer_free(sbobj); return error; diff --git a/common/ini/ini_print.c b/common/ini/ini_print.c index 60771d3..f12b27e 100644 --- a/common/ini/ini_print.c +++ b/common/ini/ini_print.c @@ -388,3 +388,25 @@ void print_config_parsing_errors(FILE *file, TRACE_FLOW_STRING("print_config_parsing_errors", "Exit"); } + + +/* Function to print errors from the list */ +void ini_print_errors(FILE *file, char **error_list) +{ + unsigned count = 0; + + TRACE_FLOW_ENTRY(); + + if (!error_list) { + TRACE_FLOW_STRING("List is empty.", ""); + return; + } + + while (error_list[count]) { + fprintf(file, "%s\n", error_list[count]); + count++; + } + + TRACE_FLOW_EXIT(); + return; +} diff --git a/common/ini/ini_serialize.c b/common/ini/ini_serialize.c index 9738be9..177979a 100644 --- a/common/ini/ini_serialize.c +++ b/common/ini/ini_serialize.c @@ -63,7 +63,7 @@ static int ini_serialize_cb(const char *property, } /* Traverse the collection and build the serialization object */ -int ini_config_serialize(struct configobj *ini_config, +int ini_config_serialize(struct ini_cfgobj *ini_config, struct simplebuffer *sbobj) { int error = EOK; -- cgit