summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitri Pal <dpal@redhat.com>2010-06-19 11:28:04 -0400
committerDmitri Pal <dpal@redhat.com>2010-08-10 12:51:32 -0400
commitca581776574bee1f3260a5547c1f017733d24a68 (patch)
tree4e7ec0387cf7880db40891c07256c4ab510f3fb0
parentd603d8cf9bfb282aba05ea368ac1512015fffe8b (diff)
downloadsssd-ca581776574bee1f3260a5547c1f017733d24a68.tar.gz
sssd-ca581776574bee1f3260a5547c1f017733d24a68.tar.xz
sssd-ca581776574bee1f3260a5547c1f017733d24a68.zip
[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.
-rw-r--r--common/ini/Makefile.am25
-rw-r--r--common/ini/configure.ac7
-rw-r--r--common/ini/ini_parse.c997
-rw-r--r--common/ini/ini_parse.h17
-rw-r--r--common/ini/ini_parse_ut.c183
5 files changed, 1219 insertions, 10 deletions
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 <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/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 <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/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 <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 "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;
+}