summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitri Pal <dpal@redhat.com>2010-06-19 11:28:04 -0400
committerStephen Gallagher <sgallagh@redhat.com>2010-09-22 14:57:53 -0400
commitbcfbcf2e8d578580f308b5bdded49b166650f131 (patch)
treea1348fb14bf6a45bff16d668c1e083042252d569
parent49ea94d1755e9003cbc2cba99c218cdd0a391813 (diff)
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.
-rw-r--r--Makefile.am21
-rw-r--r--configure.ac6
-rw-r--r--contrib/ding-libs.spec.in4
-rw-r--r--ini/ini_parse.c997
-rw-r--r--ini/ini_parse.h17
-rw-r--r--ini/ini_parse_ut.c209
6 files changed, 1248 insertions, 6 deletions
diff --git a/Makefile.am b/Makefile.am
index 47f5329..da36c20 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -231,6 +231,10 @@ libini_config_la_SOURCES = \
ini/ini_comment.h \
ini/ini_valueobj.c \
ini/ini_valueobj.h \
+ ini/ini_serialize.c \
+ ini/ini_configobj.c \
+ ini/ini_configobj.h \
+ ini/ini_config_priv.h \
trace/trace.h
libini_config_la_LIBADD = \
libcollection.la \
@@ -238,7 +242,7 @@ libini_config_la_LIBADD = \
libref_array.la \
libbasicobjects.la
libini_config_la_LDFLAGS = \
- -version-info 2:0:0
+ -version-info 3:0:0
dist_noinst_DATA += \
ini/ini.conf \
@@ -250,12 +254,14 @@ dist_noinst_DATA += \
check_PROGRAMS += \
ini_config_ut \
ini_comment_ut \
- ini_valueobj_ut
+ ini_valueobj_ut \
+ ini_parse_ut
TESTS += \
ini_config_ut \
ini_comment_ut \
- ini_valueobj_ut
+ ini_valueobj_ut \
+ ini_parse_ut
ini_config_ut_SOURCES = ini/ini_config_ut.c
ini_config_ut_LDADD = \
@@ -268,12 +274,19 @@ ini_comment_ut_LDADD = libini_config.la
ini_valueobj_ut_SOURCES = ini/ini_valueobj_ut.c
ini_valueobj_ut_LDADD = libini_config.la
+ini_parse_ut_SOURCES = ini/ini_parse_ut.c
+ini_parse_ut_LDADD = libini_config.la
+
ini_config-docs:
if HAVE_DOXYGEN
cd ini; \
$(DOXYGEN) ini_config.cfg.doxy
endif
+clean-local-ini_config:
+ rm -f ./*.out
+ rm -f test.ini
+
##############################################################################
# Additional rules
##############################################################################
@@ -322,5 +335,5 @@ tests: all $(check_PROGRAMS)
docs: path_utils-docs collection-docs ref_array-docs ini_config-docs basicobjects-docs
-clean-local:
+clean-local: clean-local-ini_config
rm -Rf doc
diff --git a/configure.ac b/configure.ac
index 906a183..10c4062 100644
--- a/configure.ac
+++ b/configure.ac
@@ -47,6 +47,12 @@ AC_CHECK_FUNC([strcasestr],
[Define if strcasestr exists]),
AC_MSG_ERROR("Platform must support strcasestr"))
+AC_CHECK_FUNC([getline],
+ AC_DEFINE([HAVE_GETLINE],
+ [1],
+ [Define if getline() exists]),
+ AC_MSG_ERROR("Platform must support getline()"))
+
AC_DEFINE([COL_MAX_DATA], [65535], [Max length of the data block allowed in the collection value.])
AC_DEFINE([MAX_KEY], [1024], [Max length of the key in the INI file.])
diff --git a/contrib/ding-libs.spec.in b/contrib/ding-libs.spec.in
index d7b3561..32e191e 100644
--- a/contrib/ding-libs.spec.in
+++ b/contrib/ding-libs.spec.in
@@ -297,8 +297,8 @@ structure
%defattr(-,root,root,-)
%doc COPYING
%doc COPYING.LESSER
-%{_libdir}/libini_config.so.2
-%{_libdir}/libini_config.so.2.0.0
+%{_libdir}/libini_config.so.3
+%{_libdir}/libini_config.so.3.0.0
%files -n libini_config-devel
%defattr(-,root,root,-)
diff --git a/ini/ini_parse.c b/ini/ini_parse.c
index 7b3785c..adc678e 100644
--- a/ini/ini_parse.c
+++ b/ini/ini_parse.c
@@ -23,13 +23,65 @@
#include <errno.h>
#include <string.h>
#include <ctype.h>
+/* For error text */
+#include <libintl.h>
+#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/ini/ini_parse.h b/ini/ini_parse.h
index 56a0db9..7eb9fdc 100644
--- a/ini/ini_parse.h
+++ b/ini/ini_parse.h
@@ -23,6 +23,8 @@
#define INI_PARSE_H
#include <stdio.h>
+#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/ini/ini_parse_ut.c b/ini/ini_parse_ut.c
new file mode 100644
index 0000000..08e55f6
--- /dev/null
+++ b/ini/ini_parse_ut.c
@@ -0,0 +1,209 @@
+/*
+ INI LIBRARY
+
+ Unit test for the parser object.
+
+ Copyright (C) Dmitri Pal <dpal@redhat.com> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "ini_defines.h"
+#include "ini_parse.h"
+#include "ini_config.h"
+#include "ini_configobj.h"
+#include "simplebuffer.h"
+#include "path_utils.h"
+#include "config.h"
+#define TRACE_HOME
+#include "trace.h"
+
+int verbose = 0;
+char *confdir = NULL;
+
+#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;
+ char filename_base[96];
+
+ 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 for reading. 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;
+ }
+
+ error = get_basename(filename_base, 96, filename);
+ sprintf(new_file, "%s.out", filename_base);
+
+ errno = 0;
+ ff = fopen(new_file, "w");
+ if(!ff) {
+ error = errno;
+ printf("Failed to open file for writing. 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;
+ char *files[5];
+
+ files[0] = malloc(sizeof(char)*512);
+ sprintf(files[0], "%s/ini.d/real.conf", confdir);
+ files[1] = malloc(sizeof(char)*512);
+ sprintf(files[1], "%s/ini.d/mysssd.conf", confdir);
+ files[2] = malloc(sizeof(char)*512);
+ sprintf(files[2], "%s/ini.d/ipa.conf", confdir);
+ files[3] = malloc(sizeof(char)*512);
+ sprintf(files[3], "%s/ini.d/test.conf", confdir);
+ files[4] = 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++;
+ }
+
+ free(files[3]);
+ free(files[2]);
+ free(files[1]);
+ free(files[0]);
+
+ return lasterr;
+}
+
+/* Main function of the unit test */
+int main(int argc, char *argv[])
+{
+ int error = 0;
+ test_fn tests[] = { read_save_test,
+ NULL };
+ char *srcdir;
+ 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"));
+
+ srcdir = getenv("srcdir");
+ if(!srcdir) {
+ confdir = malloc(sizeof(char)*3);
+ sprintf(confdir, "./ini");
+ } else {
+ confdir = malloc(strlen(srcdir)+4*sizeof(char));
+ sprintf(confdir, "%s/ini", srcdir);
+ }
+
+ while ((t = tests[i++])) {
+ error = t();
+ if (error) {
+ INIOUT(printf("Failed with error %d!\n", error));
+ return error;
+ }
+ }
+
+ INIOUT(printf("Success!\n"));
+
+ return 0;
+}