From ca581776574bee1f3260a5547c1f017733d24a68 Mon Sep 17 00:00:00 2001 From: Dmitri Pal Date: Sat, 19 Jun 2010 11:28:04 -0400 Subject: [INI] New INI parser The parser is added to the existing module. The old parsing functuion will be removed when we switch to the new interface. Parser logic: * There is one high level function that wraps the parser interface. It is at the bottom of the module. ini_parse_config(); * Internally it creates a perser object and then runs parser on it. * At the end parser object is destroyed. * This object stores the state of the parser. * The parser has an action queue * There are several actions that parser can perform - read line - inspect read line - record an error - process last comment in the file (POST) * Each action handler determines what to do next depending upon what has happened. * Read handler reads lines and enqueues inspection action in case of success or error action in case of failure. * Inspection action parses last read line and treats it either is a: * Comment * Section * New key + value * Continuation of the value In case of error the error action is enqueued. * Error can be fatal or non fatal. It depend on the error_level flag passed in. If the error is non fatal the read action is enqueued otherwise parser stops. * The POST action is a special action to handle comment at the bottom of the file. The comment is stored with the value it preceeds so in case there is a comment at the bottom of the file a special value needs to be created to hold just the comment. --- common/ini/Makefile.am | 25 +- common/ini/configure.ac | 7 + common/ini/ini_parse.c | 997 ++++++++++++++++++++++++++++++++++++++++++++++ common/ini/ini_parse.h | 17 + common/ini/ini_parse_ut.c | 183 +++++++++ 5 files changed, 1219 insertions(+), 10 deletions(-) create mode 100644 common/ini/ini_parse_ut.c diff --git a/common/ini/Makefile.am b/common/ini/Makefile.am index eca3bb0..b5049bc 100644 --- a/common/ini/Makefile.am +++ b/common/ini/Makefile.am @@ -27,6 +27,7 @@ dist_noinst_DATA = \ ini.conf \ ini.d/real.conf \ ini.d/test.conf \ + ini.d/mysssd.conf \ m4 dist_include_HEADERS = \ @@ -49,7 +50,11 @@ libini_config_la_SOURCES = \ ini_comment.c \ ini_comment.h \ ini_valueobj.c \ - ini_valueobj.h + ini_valueobj.h \ + ini_serialize.c \ + ini_configobj.c \ + ini_configobj.h \ + ini_config_priv.h libini_config_la_LIBADD = \ @@ -58,23 +63,22 @@ libini_config_la_LIBADD = \ -L$(topbuilddir)/refarray \ -L$(topbuilddir)/basicobjects \ -lcollection \ + -lbasicobjects \ -lref_array \ -lpath_utils libini_config_la_LDFLAGS = \ -version-info 2:0:0 # Build unit test -check_PROGRAMS = ini_config_ut ini_comment_ut ini_valueobj_ut +check_PROGRAMS = ini_config_ut ini_comment_ut ini_valueobj_ut ini_parse_ut ini_config_ut_SOURCES = ini_config_ut.c -ini_config_ut_LDADD = libini_config.la \ - -L$(topbuilddir)/collection -lcollection \ - -L$(topbuilddir)/basicobjects -lbasicobjects +ini_config_ut_LDADD = libini_config.la ini_comment_ut_SOURCES = ini_comment_ut.c -ini_comment_ut_LDADD = libini_config.la \ - -L$(topbuilddir)/basicobjects -lbasicobjects +ini_comment_ut_LDADD = libini_config.la ini_valueobj_ut_SOURCES = ini_valueobj_ut.c -ini_valueobj_ut_LDADD = libini_config.la \ - -L$(topbuilddir)/basicobjects -lbasicobjects +ini_valueobj_ut_LDADD = libini_config.la +ini_parse_ut_SOURCES = ini_parse_ut.c +ini_parse_ut_LDADD = libini_config.la if HAVE_DOXYGEN docs: @@ -88,9 +92,10 @@ docs: @exit 1 endif -TESTS = ini_config_ut ini_comment_ut ini_valueobj_ut +TESTS = ini_config_ut ini_comment_ut ini_valueobj_ut ini_parse_ut tests: all $(check_PROGRAMS) clean-local: + rm -f ./ini.d/*.out rm -Rf doc diff --git a/common/ini/configure.ac b/common/ini/configure.ac index eb3e6f2..8436bff 100644 --- a/common/ini/configure.ac +++ b/common/ini/configure.ac @@ -21,6 +21,13 @@ AC_ARG_ENABLE([trace], [trace_level="0"]) AS_IF([test ["$trace_level" -gt "0"] -a ["$trace_level" -lt "8"] ],[AC_SUBST([TRACE_VAR],["-DTRACE_LEVEL=$trace_level"])]) +AC_CHECK_FUNC([getline], + AC_DEFINE([HAVE_GETLINE], + [1], + [Define if getline() exists]), + AC_MSG_ERROR("Platform must support getline()")) + + AC_DEFINE([MAX_KEY], [1024], [Max length of the key in the INI file.]) AC_PATH_PROG([DOXYGEN], [doxygen], [false]) diff --git a/common/ini/ini_parse.c b/common/ini/ini_parse.c index 7b3785c..adc678e 100644 --- a/common/ini/ini_parse.c +++ b/common/ini/ini_parse.c @@ -23,13 +23,65 @@ #include #include #include +/* For error text */ +#include +#define _(String) gettext (String) #include "config.h" #include "trace.h" #include "ini_parse.h" #include "ini_defines.h" #include "ini_config.h" +#include "ini_valueobj.h" +#include "ini_config_priv.h" +#include "collection.h" +#include "collection_queue.h" +#define INI_WARNING 0xA0000000 /* Warning bit */ +struct parser_obj { + /* Externally passed and saved data */ + FILE *file; + struct collection_item *top; + struct collection_item *el; + struct collection_item **el_acceptor; + const char *filename; + int error_level; + /* Wrapping boundary */ + uint32_t boundary; + /* Action queue */ + struct collection_item *queue; + /* Last error */ + uint32_t last_error; + /* Last line number */ + uint32_t linenum; + /* Line number of the last found key */ + uint32_t keylinenum; + /* Internal variables */ + struct collection_item *sec; + struct ini_comment *ic; + char *last_read; + uint32_t last_read_len; + char *key; + uint32_t key_len; + struct ref_array *raw_lines; + struct ref_array *raw_lengths; + int ret; +}; + +typedef int (*action_fn)(struct parser_obj *); + + +#define PARSE_ACTION "action" + +/* Actions */ +#define PARSE_READ 0 /* Read from the file */ +#define PARSE_INSPECT 1 /* Process read string */ +#define PARSE_POST 2 /* Reading is complete */ +#define PARSE_ERROR 3 /* Handle error */ +#define PARSE_DONE 4 /* We are done */ + + +/* THIS FUNCTION WILL BE REMOVED AS SOON AS WE SWITCH TO THE NEW INTERFACE */ /* Reads a line from the file */ int read_line(FILE *file, char *buf, @@ -188,3 +240,948 @@ int read_line(FILE *file, TRACE_FLOW_STRING("read_line", "Exit"); return RET_PAIR; } + +/************************************************************/ +/* REMOVE FUNCTION ABOVE */ +/************************************************************/ + +/* Destroy parser object */ +void parser_destroy(struct parser_obj *po) +{ + TRACE_FLOW_ENTRY(); + + if(po) { + col_destroy_queue(po->queue); + col_destroy_collection_with_cb(po->sec, ini_cleanup_cb, NULL); + ini_comment_destroy(po->ic); + value_destroy_arrays(po->raw_lines, + po->raw_lengths); + if (po->last_read) free(po->last_read); + if (po->key) free(po->key); + free(po); + } + + TRACE_FLOW_EXIT(); +} + +/* Create parse object + * + * It assumes that the ini collection + * has been precreated. + */ +int parser_create(FILE *file, + const char *config_filename, + struct collection_item *ini_config, + int error_level, + struct collection_item **error_list, + uint32_t boundary, + struct parser_obj **po) +{ + int error = EOK; + struct parser_obj *new_po = NULL; + + TRACE_FLOW_ENTRY(); + + /* Make sure that all the parts are initialized */ + if ((!po) || + (!file) || + (!config_filename) || + (!ini_config) || + (!error_list)) { + TRACE_ERROR_NUMBER("Invalid argument", EINVAL); + return EINVAL; + } + + if ((error_level != INI_STOP_ON_ANY) && + (error_level != INI_STOP_ON_NONE) && + (error_level != INI_STOP_ON_ERROR)) { + TRACE_ERROR_NUMBER("Invalid argument", EINVAL); + return EINVAL; + } + + new_po = malloc(sizeof(struct parser_obj)); + if (!new_po) { + TRACE_ERROR_NUMBER("No memory", ENOMEM); + return ENOMEM; + } + + /* Save external data */ + new_po->file = file; + new_po->top = ini_config; + new_po->el_acceptor = error_list; + new_po->filename = config_filename; + new_po->error_level = error_level; + new_po->boundary = boundary; + + /* Initialize internal varibles */ + new_po->sec = NULL; + new_po->ic = NULL; + new_po->last_error = 0; + new_po->linenum = 0; + new_po->last_read = NULL; + new_po->last_read_len = 0; + new_po->key = NULL; + new_po->key_len = 0; + 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; + error = col_create_queue(&(new_po->queue)); + if (error) { + TRACE_ERROR_NUMBER("Failed to create queue", error); + parser_destroy(new_po); + return error; + } + + error = col_enqueue_unsigned_property(new_po->queue, + PARSE_ACTION, + PARSE_READ); + if (error) { + TRACE_ERROR_NUMBER("Failed to create queue", error); + parser_destroy(new_po); + return error; + } + + *po = new_po; + + TRACE_FLOW_EXIT(); + return error; +} + +/* Function to read next line from the file */ +int parser_read(struct parser_obj *po) +{ + int error = EOK; + char *buffer = NULL; + ssize_t res = 0; + size_t len = 0; + int32_t i = 0; + uint32_t action; + + TRACE_FLOW_ENTRY(); + + /* Adjust line number */ + (po->linenum)++; + + /* Get line from the file */ + res = getline(&buffer, &len, po->file); + if (res == -1) { + if (feof(po->file)) { + TRACE_FLOW_STRING("Read nothing", ""); + action = PARSE_POST; + } + else { + TRACE_ERROR_STRING("Error reading", ""); + action = PARSE_ERROR; + po->last_error = ERR_READ; + } + if(buffer) free(buffer); + } + else { + /* Read Ok */ + len = res; + TRACE_INFO_STRING("Read line ok:", buffer); + TRACE_INFO_NUMBER("Length:", len); + TRACE_INFO_NUMBER("Strlen:", strlen(buffer)); + + if (buffer[0] == '\0') { + /* Empty line - read again (should not ever happen) */ + action = PARSE_READ; + free(buffer); + } + else { + /* Check length */ + if (len >= BUFFER_SIZE) { + TRACE_ERROR_STRING("Too long", ""); + action = PARSE_ERROR; + po->last_error = ERR_LONGDATA; + free(buffer); + } + else { + /* Trim end line */ + i = len - 1; + while ((i >= 0) && + ((buffer[i] == '\r') || + (buffer[i] == '\n'))) { + TRACE_INFO_NUMBER("Offset:", i); + TRACE_INFO_NUMBER("Code:", buffer[i]); + buffer[i] = '\0'; + i--; + } + + po->last_read = buffer; + po->last_read_len = i + 1; + action = PARSE_INSPECT; + TRACE_INFO_STRING("Line:", po->last_read); + TRACE_INFO_NUMBER("Linelen:", po->last_read_len); + } + } + } + + /* Move to the next action */ + error = col_enqueue_unsigned_property(po->queue, + PARSE_ACTION, + action); + if (error) { + TRACE_ERROR_NUMBER("Failed to schedule an action", error); + return error; + } + + TRACE_FLOW_EXIT(); + return EOK; +} + +/* Complete value processing */ +static int complete_value_processing(struct parser_obj *po) +{ + int error = EOK; + struct value_obj *vo = NULL; + + TRACE_FLOW_ENTRY(); + + /* If there is not open section create a default one */ + if(!(po->sec)) { + /* Create a new section */ + error = col_create_collection(&po->sec, + INI_DEFAULT_SECTION, + COL_CLASS_INI_SECTION); + if (error) { + TRACE_ERROR_NUMBER("Failed to create default section", error); + return error; + } + } + + /* Construct value object from what we have */ + error = value_create_from_refarray(po->raw_lines, + po->raw_lengths, + po->keylinenum, + INI_VALUE_READ, + po->key_len, + po->boundary, + po->ic, + &vo); + + if (error) { + TRACE_ERROR_NUMBER("Failed to create value object", error); + return error; + } + + /* Forget about the arrays. They are now owned by the value object */ + po->ic = NULL; + po->raw_lines = NULL; + po->raw_lengths = NULL; + + + /* Add value to collection */ + error = col_add_binary_property(po->sec, + NULL, + po->key, + &vo, + sizeof(struct value_obj *)); + if (error) { + TRACE_ERROR_NUMBER("Failed to add value object to the section", error); + value_destroy(vo); + return error; + } + + free(po->key); + po->key = NULL; + po->key_len = 0; + TRACE_FLOW_EXIT(); + return EOK; +} + + +/* Process comment */ +static int handle_comment(struct parser_obj *po, uint32_t *action) +{ + int error = EOK; + + TRACE_FLOW_ENTRY(); + + /* We got a comment */ + if (po->key) { + /* Previous value if any is complete */ + error = complete_value_processing(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to finish saving value", error); + return error; + } + } + + if (!(po->ic)) { + /* Create a new comment */ + error = ini_comment_create(&(po->ic)); + if (error) { + TRACE_ERROR_NUMBER("Failed to create comment", error); + return error; + } + } + + /* Add line to comment */ + error = ini_comment_build_wl(po->ic, + po->last_read, + po->last_read_len); + if (error) { + TRACE_ERROR_NUMBER("Failed to add line to comment", error); + return error; + } + /* + * We are done with the comment line. + * Free it since comment keeps a copy. + */ + free(po->last_read); + po->last_read = NULL; + po->last_read_len = 0; + *action = PARSE_READ; + + TRACE_FLOW_EXIT(); + return EOK; +} + + +/* Process line starts with space */ +static int handle_space(struct parser_obj *po, uint32_t *action) +{ + int error = EOK; + + TRACE_FLOW_ENTRY(); + + /* Do we have current value object? */ + if (po->key) { + /* This is a new line in a folded value */ + error = value_add_to_arrays(po->last_read, + po->last_read_len, + po->raw_lines, + po->raw_lengths); + if (error) { + TRACE_ERROR_NUMBER("Failed to add line to value", error); + return error; + } + /* Do not free the line, it is now an element of the array */ + po->last_read = NULL; + po->last_read_len = 0; + *action = PARSE_READ; + } + else { + /* We do not have an active value + * but have a line is starting with a space. + * For now it is error. + * We can change it in future if + * people find it being too restrictive + */ + *action = PARSE_ERROR; + po->last_error = ERR_SPACE; + } + + TRACE_FLOW_EXIT(); + return EOK; +} + +/* Handle key-value pair */ +static int handle_kvp(struct parser_obj *po, uint32_t *action) +{ + int error = EOK; + char *eq = NULL; + uint32_t len = 0; + char *dupval = NULL; + + TRACE_FLOW_ENTRY(); + + /* We got a line with KVP */ + if (*(po->last_read) == '=') { + po->last_error = ERR_NOKEY; + *action = PARSE_ERROR; + return EOK; + } + + /* Find "=" */ + eq = strchr(po->last_read, '='); + if (eq == NULL) { + TRACE_ERROR_STRING("No equal sign", po->last_read); + po->last_error = ERR_NOEQUAL; + *action = PARSE_ERROR; + return EOK; + } + + /* Strip spaces around "=" */ + /* Since eq > po->last_read we can substract 1 */ + len = eq - po->last_read - 1; + while ((len > 0) && (isspace(*(po->last_read + len)))) len--; + /* Adjust length properly */ + len++; + if (!len) { + TRACE_ERROR_STRING("No key", po->last_read); + po->last_error = ERR_NOKEY; + *action = PARSE_ERROR; + return EOK; + } + + /* Check the key length */ + if(len >= MAX_KEY) { + TRACE_ERROR_STRING("Key name is too long", po->last_read); + po->last_error = ERR_LONGKEY; + *action = PARSE_ERROR; + return EOK; + } + + if (po->key) { + /* Complete processing of the previous value */ + error = complete_value_processing(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to complete value processing", error); + return error; + } + } + + /* Dup the key name */ + errno = 0; + po->key = malloc(len + 1); + if (!(po->key)) { + error = errno; + TRACE_ERROR_NUMBER("Failed to dup key", error); + return error; + } + + memcpy(po->key, po->last_read, len); + *(po->key + len) = '\0'; + po->key_len = len; + + TRACE_INFO_STRING("Key:", po->key); + TRACE_INFO_NUMBER("Keylen:", po->key_len); + + len = po->last_read_len - (eq - po->last_read) - 1; + + /* Trim spaces after equal sign */ + eq++; + while (isspace(*eq)) { + eq++; + len--; + } + + TRACE_INFO_STRING("VALUE:", eq); + TRACE_INFO_NUMBER("LENGTH:", len); + + /* Dup the part of the value */ + errno = 0; + dupval = malloc(len + 1); + if (!dupval) { + error = errno; + TRACE_ERROR_NUMBER("Failed to dup value", error); + return error; + } + + memcpy(dupval, eq, len); + *(dupval + len) = '\0'; + + /* Create new arrays */ + error = value_create_arrays(&(po->raw_lines), + &(po->raw_lengths)); + if (error) { + TRACE_ERROR_NUMBER("Failed to create arrays", error); + free(dupval); + return error; + } + + /* Save a duplicated part in the value */ + error = value_add_to_arrays(dupval, + len, + po->raw_lines, + po->raw_lengths); + + if (error) { + TRACE_ERROR_NUMBER("Failed to add value to arrays", error); + free(dupval); + return error; + } + + /* Save the line number of the last found key */ + po->keylinenum = po->linenum; + + /* Prepare for reading */ + free(po->last_read); + po->last_read = NULL; + po->last_read_len = 0; + + *action = PARSE_READ; + + TRACE_FLOW_EXIT(); + return EOK; +} + + +/* Parse and process section */ +static int handle_section(struct parser_obj *po, uint32_t *action) +{ + int error = EOK; + char *start; + char *end; + char *dupval; + uint32_t len; + + TRACE_FLOW_ENTRY(); + + /* We are safe to substract 1 + * since we know that there is at + * least one character on the line + * based on the check above. + */ + end = po->last_read + po->last_read_len - 1; + while (isspace(*end)) end--; + if (*end != ']') { + *action = PARSE_ERROR; + po->last_error = ERR_NOCLOSESEC; + return EOK; + } + + /* Skip spaces at the beginning of the section name */ + start = po->last_read + 1; + while (isspace(*start)) start++; + + /* Check if there is a section name */ + if (start == end) { + *action = PARSE_ERROR; + po->last_error = ERR_NOSECTION; + return EOK; + } + + /* Skip spaces at the end of the section name */ + end--; + while (isspace(*end)) end--; + + /* We got section name */ + len = end - start + 1; + + if (len > MAX_KEY) { + *action = PARSE_ERROR; + po->last_error = ERR_SECTIONLONG; + return EOK; + } + + if (po->key) { + /* Complete processing of the previous value */ + error = complete_value_processing(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to complete value processing", error); + return error; + } + } + + /* Do we have an active section? */ + if (po->sec) { + /* Save it */ + error = col_add_collection_to_collection(po->top, + NULL, NULL, + po->sec, + COL_ADD_MODE_EMBED); + if (error) { + TRACE_ERROR_NUMBER("Failed to save section", error); + return error; + } + po->sec = NULL; + } + + /* Dup the name */ + errno = 0; + dupval = malloc(len + 1); + if (!dupval) { + error = errno; + TRACE_ERROR_NUMBER("Failed to dup section name", error); + return error; + } + + memcpy(dupval, start, len); + dupval[len] = '\0'; + + /* Create a new section */ + error = col_create_collection(&po->sec, + dupval, + COL_CLASS_INI_SECTION); + if (error) { + TRACE_ERROR_NUMBER("Failed to create a section", error); + free(dupval); + return error; + } + + /* But if there is just a comment then create a special key */ + po->key_len = sizeof(INI_SECTION_KEY) - 1; + po->key = strndup(INI_SECTION_KEY, sizeof(INI_SECTION_KEY)); + /* Create new arrays */ + error = value_create_arrays(&(po->raw_lines), + &(po->raw_lengths)); + if (error) { + TRACE_ERROR_NUMBER("Failed to create arrays", error); + free(dupval); + return error; + } + + /* Save a duplicated part in the value */ + error = value_add_to_arrays(dupval, + len, + po->raw_lines, + po->raw_lengths); + if (error) { + TRACE_ERROR_NUMBER("Failed to add value to the arrays", error); + free(dupval); + return error; + } + + /* Complete processing of this value */ + error = complete_value_processing(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to complete value processing", error); + return error; + } + + /* We are done dealing with section */ + free(po->last_read); + po->last_read = NULL; + po->last_read_len = 0; + *action = PARSE_READ; + + TRACE_FLOW_EXIT(); + return EOK; + +} + +int is_just_spaces(const char *str, uint32_t len) +{ + uint32_t i; + + TRACE_FLOW_ENTRY(); + + for (i = 0; i < len; i++) { + if (!isspace(str[i])) return 0; + } + + TRACE_FLOW_EXIT(); + return 1; +} + +/* Inspect the line */ +static int parser_inspect(struct parser_obj *po) +{ + int error = EOK; + uint32_t action = PARSE_DONE; + + TRACE_FLOW_ENTRY(); + + if ((*(po->last_read) == '\0') || + (*(po->last_read) == ';') || + (*(po->last_read) == '#')) { + + error = handle_comment(po, &action); + if (error) { + TRACE_ERROR_NUMBER("Failed to process comment", error); + return error; + } + } + else if ((*(po->last_read) == ' ') || + (*(po->last_read) == '\t')) { + + /* Check if this is a completely empty line */ + if (is_just_spaces(po->last_read, po->last_read_len)) { + error = handle_comment(po, &action); + if (error) { + TRACE_ERROR_NUMBER("Failed to process comment", error); + return error; + } + } + else { + error = handle_space(po, &action); + if (error) { + TRACE_ERROR_NUMBER("Failed to process line wrapping", error); + return error; + } + } + } + else if (*(po->last_read) == '[') { + + error = handle_section(po, &action); + if (error) { + TRACE_ERROR_NUMBER("Failed to save section", error); + return error; + } + } + else { + + error = handle_kvp(po, &action); + if (error) { + TRACE_ERROR_NUMBER("Failed to save section", error); + return error; + } + } + + /* Move to the next action */ + error = col_enqueue_unsigned_property(po->queue, + PARSE_ACTION, + action); + if (error) { + TRACE_ERROR_NUMBER("Failed to schedule an action", error); + return error; + } + + TRACE_FLOW_EXIT(); + return error; +} + + +/* Complete file processing */ +static int parser_post(struct parser_obj *po) +{ + int error = EOK; + + TRACE_FLOW_ENTRY(); + + /* If there was just a comment at the bottom add special key */ + if((po->ic) && (!(po->key))) { + po->key_len = sizeof(INI_SPECIAL_KEY) - 1; + po->key = strndup(INI_SPECIAL_KEY, sizeof(INI_SPECIAL_KEY)); + /* Create new arrays */ + error = value_create_arrays(&(po->raw_lines), + &(po->raw_lengths)); + if (error) { + TRACE_ERROR_NUMBER("Failed to create arrays", error); + return error; + } + + } + + /* If there is a key being processed add it */ + if (po->key) { + error = complete_value_processing(po); + if (error) { + TRACE_ERROR_NUMBER("Failed to complete value processing", error); + return error; + } + } + + /* If we are done save the section */ + error = col_add_collection_to_collection(po->top, + NULL, NULL, + po->sec, + COL_ADD_MODE_EMBED); + if (error) { + TRACE_ERROR_NUMBER("Failed to save section", error); + return error; + } + + po->sec = NULL; + + /* Move to the next action */ + error = col_enqueue_unsigned_property(po->queue, + PARSE_ACTION, + PARSE_DONE); + if (error) { + TRACE_ERROR_NUMBER("Failed to schedule an action", error); + return error; + } + + TRACE_FLOW_EXIT(); + return EOK; +} + +/* Error and warning processing */ +static int parser_error(struct parser_obj *po) +{ + int error = EOK; + uint32_t action; + int idx = 0; + const char *errtxt[] = { ERROR_TXT, WARNING_TXT }; + struct 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; + } + } + + /* Exit if there was an error parsing file */ + if (po->error_level == INI_STOP_ON_ANY) { + action = PARSE_DONE; + if (po->last_error & INI_WARNING) po->ret = EILSEQ; + else po->ret = EIO; + } + else if (po->error_level == INI_STOP_ON_NONE) { + action = PARSE_READ; + if (po->ret == 0) { + if (po->last_error & INI_WARNING) po->ret = EILSEQ; + else po->ret = EIO; + } + /* It it was warning but now if it is an error + * bump to return code to indicate error. */ + else if((po->ret == EILSEQ) && + (!(po->last_error & INI_WARNING))) po->ret = EIO; + + } + else { /* Stop on error */ + if (po->last_error & INI_WARNING) { + action = PARSE_READ; + po->ret = EILSEQ; + } + else { + action = PARSE_DONE; + po->ret = EIO; + } + } + + /* Prepare for reading */ + if (action == PARSE_READ) { + if (po->last_read) { + free(po->last_read); + po->last_read = NULL; + po->last_read_len = 0; + } + } + else { + /* If we are done save the section */ + error = col_add_collection_to_collection(po->top, + NULL, NULL, + po->sec, + COL_ADD_MODE_EMBED); + if (error) { + TRACE_ERROR_NUMBER("Failed to save section", error); + return error; + } + po->sec = NULL; + } + + /* Move to the next action */ + error = col_enqueue_unsigned_property(po->queue, + PARSE_ACTION, + action); + if (error) { + TRACE_ERROR_NUMBER("Failed to schedule an action", error); + return error; + } + + TRACE_FLOW_EXIT(); + return EOK; +} + + +/* Run parser */ +int parser_run(struct parser_obj *po) +{ + int error = EOK; + struct collection_item *item = NULL; + uint32_t action = 0; + action_fn operations[] = { parser_read, + parser_inspect, + parser_post, + parser_error, + NULL }; + + TRACE_FLOW_ENTRY(); + + while(1) { + /* Get next action */ + item = NULL; + error = col_dequeue_item(po->queue, &item); + if (error) { + TRACE_ERROR_NUMBER("Failed to get action", error); + return error; + } + + /* Get action, run operation */ + action = *((uint32_t *)(col_get_item_data(item))); + col_delete_item(item); + + 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; + } + + error = operations[action](po); + if (error) { + TRACE_ERROR_NUMBER("Failed to perform an action", error); + return error; + } + + } + + TRACE_FLOW_EXIT(); + return error; +} + +/* Top level wrapper around the parser */ +int ini_parse_config(FILE *file, + const char *config_filename, + struct configobj *ini_config, + int error_level, + struct collection_item **error_list, + uint32_t boundary) +{ + int error = EOK; + struct parser_obj *po; + + TRACE_FLOW_ENTRY(); + + if ((!ini_config) || (!(ini_config->cfg))) { + TRACE_ERROR_NUMBER("Invalid argument", EINVAL); + return EINVAL; + } + + error = parser_create(file, + config_filename, + ini_config->cfg, + error_level, + error_list, + boundary, + &po); + if (error) { + TRACE_ERROR_NUMBER("Failed to perform an action", error); + return error; + } + + error = parser_run(po); + + parser_destroy(po); + + TRACE_INFO_NUMBER("Parsing returned:", error); + TRACE_FLOW_EXIT(); + return error; + +} diff --git a/common/ini/ini_parse.h b/common/ini/ini_parse.h index 56a0db9..7eb9fdc 100644 --- a/common/ini/ini_parse.h +++ b/common/ini/ini_parse.h @@ -23,6 +23,8 @@ #define INI_PARSE_H #include +#include "collection.h" +#include "ini_configobj.h" /* Internal function to read line from INI file */ int read_line(FILE *file, @@ -33,4 +35,19 @@ int read_line(FILE *file, int *length, int *ext_error); +/*************************************************************************/ +/* THIS INTERFACE WILL CHANGE WHEN THE FILE CONTEXT OBJECT IS INTRODUCED */ +/*************************************************************************/ +/* NOTE: Consider moving the boundary into the config object rather than + * have it as a part of the parser - TBD. + */ + +/* Parse a configration file */ +int ini_parse_config(FILE *file, + const char *config_filename, + struct configobj *ini_config, + int error_level, + struct collection_item **error_list, + uint32_t boundary); + #endif diff --git a/common/ini/ini_parse_ut.c b/common/ini/ini_parse_ut.c new file mode 100644 index 0000000..c66d250 --- /dev/null +++ b/common/ini/ini_parse_ut.c @@ -0,0 +1,183 @@ +/* + INI LIBRARY + + Unit test for the parser object. + + 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 . +*/ + +#include +#include +#include +#include +#include "ini_defines.h" +#include "ini_parse.h" +#include "ini_config.h" +#include "ini_configobj.h" +#include "simplebuffer.h" +#include "config.h" +#define TRACE_HOME +#include "trace.h" + +int verbose = 0; + +#define INIOUT(foo) \ + do { \ + if (verbose) foo; \ + } while(0) + +typedef int (*test_fn)(void); + +int test_one_file(const char *filename) +{ + int error = EOK; + FILE *ff = NULL; + char new_file[100]; + struct configobj *ini_config = NULL; + struct collection_item *error_list = NULL; + struct simplebuffer *sbobj = NULL; + uint32_t left = 0; + + INIOUT(printf("<==== Testing file %s ====>\n", filename)); + + /* Create config collection */ + error = ini_config_create(&ini_config); + if (error != EOK) { + printf("Failed to create collection. Error %d.\n", error); + return error; + } + + errno = 0; + ff = fopen(filename,"r"); + if(!ff) { + error = errno; + printf("Failed to open file. Error %d.\n", error); + ini_config_destroy(ini_config); + return error; + } + + error = ini_parse_config(ff, + filename, + ini_config, + INI_STOP_ON_NONE, + &error_list, + 80); + fclose(ff); + if (error != EOK) { + INIOUT(printf("Failed to parse configuration. Error %d.\n", error)); + INIOUT(print_file_parsing_errors(stdout, error_list)); + col_destroy_collection(error_list); + } + + error = simplebuffer_alloc(&sbobj); + if (error) { + TRACE_ERROR_NUMBER("Failed to allocate dynamic string.", error); + ini_config_destroy(ini_config); + return error; + } + + error = ini_serialize_config(ini_config, sbobj); + if (error != EOK) { + printf("Failed to parse configuration. Error %d.\n", error); + ini_config_destroy(ini_config); + simplebuffer_free(sbobj); + return error; + } + + sprintf(new_file, "%s.out", filename); + + errno = 0; + ff = fopen(new_file, "w"); + if(!ff) { + error = errno; + printf("Failed to open file. Error %d.\n", error); + ini_config_destroy(ini_config); + simplebuffer_free(sbobj); + return error; + } + + /* Save */ + left = simplebuffer_get_len(sbobj); + while (left > 0) { + error = simplebuffer_write(fileno(ff), sbobj, &left); + if (error) { + printf("Failed to write back the configuration %d.\n", error); + simplebuffer_free(sbobj); + ini_config_destroy(ini_config); + fclose(ff); + return error; + } + } + + ini_config_destroy(ini_config); + simplebuffer_free(sbobj); + fclose(ff); + + return EOK; +} + + +/* Run tests for multiple files */ +int read_save_test(void) +{ + int error = EOK; + int i = 0; + int lasterr = EOK; + const char *files[] = { "./ini.d/real.conf", + "./ini.d/mysssd.conf", + "./ini.d/ipa.conf", + "./ini.d/test.conf", + NULL }; + + while(files[i]) { + error = test_one_file(files[i]); + INIOUT(printf("Test fo file: %s returned %d\n", files[i], error)); + if (error) lasterr = error; + i++; + } + + return lasterr; +} + +/* Main function of the unit test */ +int main(int argc, char *argv[]) +{ + int error = 0; + test_fn tests[] = { read_save_test, + NULL }; + test_fn t; + int i = 0; + char *var; + + if ((argc > 1) && (strcmp(argv[1], "-v") == 0)) verbose = 1; + else { + var = getenv("COMMON_TEST_VERBOSE"); + if (var) verbose = 1; + } + + INIOUT(printf("Start\n")); + + while ((t = tests[i++])) { + error = t(); + if (error) { + INIOUT(printf("Failed with error %d!\n", error)); + return error; + } + } + + INIOUT(printf("Success!\n")); + return 0; +} -- cgit