[libvirt] [PATCH 08/14] Add a virKeyfilePtr object for parsing '.ini' files

Daniel P. Berrange berrange at redhat.com
Tue Mar 20 17:33:32 UTC 2012


From: "Daniel P. Berrange" <berrange at redhat.com>

The '.ini' file format is a useful alternative to the existing
config file style, when you need to have config files which
are hashes of hashes. The 'virKeyFilePtr' object provides a
way to parse these file types.

* src/Makefile.am, src/util/virkeyfile.c,
  src/util/virkeyfile.h: Add .ini file parser
* tests/Makefile.am, tests/virkeyfiletest.c: Test
  basic parsing capabilities
---
 po/POTFILES.in           |    1 +
 src/Makefile.am          |    1 +
 src/libvirt_private.syms |   10 ++
 src/util/virkeyfile.c    |  367 ++++++++++++++++++++++++++++++++++++++++++++++
 src/util/virkeyfile.h    |   64 ++++++++
 tests/Makefile.am        |    8 +-
 tests/virkeyfiletest.c   |  123 ++++++++++++++++
 7 files changed, 573 insertions(+), 1 deletions(-)
 create mode 100644 src/util/virkeyfile.c
 create mode 100644 src/util/virkeyfile.h
 create mode 100644 tests/virkeyfiletest.c

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 16a3f9e..8354c09 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -127,6 +127,7 @@ src/util/util.c
 src/util/viraudit.c
 src/util/virfile.c
 src/util/virhash.c
+src/util/virkeyfile.c
 src/util/virnetdev.c
 src/util/virnetdevbridge.c
 src/util/virnetdevmacvlan.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 39076cc..07d7faa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -90,6 +90,7 @@ UTIL_SOURCES =							\
 		util/virhash.c util/virhash.h			\
 		util/virhashcode.c util/virhashcode.h           \
 		util/virkeycode.c util/virkeycode.h		\
+		util/virkeyfile.c util/virkeyfile.h		\
 		util/virkeymaps.h				\
 		util/virmacaddr.h util/virmacaddr.c		\
 		util/virnetdev.h util/virnetdev.c		\
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 8a14838..3f69ec1 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1198,6 +1198,16 @@ virKeycodeValueFromString;
 virKeycodeValueTranslate;
 
 
+# virkeyfile.h
+virKeyFileNew;
+virKeyFileLoadFile;
+virKeyFileLoadData;
+virKeyFileFree;
+virKeyFileHasValue;
+virKeyFileHasGroup;
+virKeyFileGetValueString;
+
+
 # virmacaddr.h
 virMacAddrCompare;
 virMacAddrFormat;
diff --git a/src/util/virkeyfile.c b/src/util/virkeyfile.c
new file mode 100644
index 0000000..3dd4960
--- /dev/null
+++ b/src/util/virkeyfile.c
@@ -0,0 +1,367 @@
+/*
+ * virkeyfile.c: "ini"-style configuration file handling
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Authors:
+ *     Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "c-ctype.h"
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+#include "virhash.h"
+#include "virkeyfile.h"
+#include "virterror_internal.h"
+
+#define VIR_FROM_THIS VIR_FROM_CONF
+
+typedef struct _virKeyFileGroup virKeyFileGroup;
+typedef virKeyFileGroup *virKeyFileGroupPtr;
+
+typedef struct _virKeyFileParserCtxt virKeyFileParserCtxt;
+typedef virKeyFileParserCtxt *virKeyFileParserCtxtPtr;
+
+struct _virKeyFile {
+    virHashTablePtr groups;
+};
+
+struct _virKeyFileParserCtxt {
+    virKeyFilePtr conf;
+
+    const char *filename;
+
+    const char *base;
+    const char *cur;
+    const char *end;
+    size_t line;
+
+    char *groupname;
+    virHashTablePtr group;
+};
+
+/*
+ * The grammar for the keyfile
+ *
+ * KEYFILE = (GROUP | COMMENT | BLANK )*
+ *
+ * COMMENT = ('#' | ';') [^\n]* '\n'
+ * BLANK = (' ' | '\t' )* '\n'
+ *
+ * GROUP = '[' GROUPNAME ']' '\n' (ENTRY ) *
+ * GROUPNAME = [^[]\n]+
+ *
+ * ENTRY = KEYNAME '=' VALUE
+ * VALUE = [^\n]* '\n'
+ * KEYNAME = [-a-zA-Z0-9]+
+ */
+
+#define IS_EOF (ctxt->cur >= ctxt->end)
+#define IS_EOL(c) (((c) == '\n') || ((c) == '\r'))
+#define CUR (*ctxt->cur)
+#define NEXT if (!IS_EOF) ctxt->cur++;
+
+
+#define virKeyFileError(ctxt, error, info) \
+    virKeyFileErrorHelper(__FILE__, __FUNCTION__, __LINE__, ctxt, error, info)
+static void
+virKeyFileErrorHelper(const char *file, const char *func, size_t line,
+                      virKeyFileParserCtxtPtr ctxt,
+                      virErrorNumber error, const char *info)
+{
+    /* Construct the string 'filename:line: info' if we have that. */
+    if (ctxt && ctxt->filename) {
+        virReportErrorHelper(VIR_FROM_CONF, error, file, func, line,
+                             _("%s:%zu: %s '%s'"), ctxt->filename, ctxt->line, info, ctxt->cur);
+    } else {
+        virReportErrorHelper(VIR_FROM_CONF, error, file, func, line,
+                             "%s", info);
+    }
+}
+
+
+static void virKeyFileValueFree(void *value, const void *name ATTRIBUTE_UNUSED)
+{
+    VIR_FREE(value);
+}
+
+static int virKeyFileParseGroup(virKeyFileParserCtxtPtr ctxt)
+{
+    int ret = -1;
+    const char *name;
+    NEXT;
+
+    ctxt->group = NULL;
+    VIR_FREE(ctxt->groupname);
+
+    name = ctxt->cur;
+    while (!IS_EOF && c_isascii(CUR) && CUR != ']')
+        ctxt->cur++;
+    if (CUR != ']') {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "cannot find end of group name, expected ']'");
+        return -1;
+    }
+
+    if (!(ctxt->groupname = strndup(name, ctxt->cur - name))) {
+        virReportOOMError();
+        return -1;
+    }
+
+    NEXT;
+
+    if (!(ctxt->group = virHashCreate(10, virKeyFileValueFree)))
+        goto cleanup;
+
+    if (virHashAddEntry(ctxt->conf->groups, ctxt->groupname, ctxt->group) < 0)
+        goto cleanup;
+
+    ret = 0;
+cleanup:
+    if (ret != 0) {
+        virHashFree(ctxt->group);
+        ctxt->group = NULL;
+        VIR_FREE(ctxt->groupname);
+    }
+
+    return ret;
+}
+
+static int virKeyFileParseValue(virKeyFileParserCtxtPtr ctxt)
+{
+    int ret = -1;
+    const char *keystart;
+    const char *valuestart;
+    char *key = NULL;
+    char *value = NULL;
+    size_t len;
+
+    if (!ctxt->groupname || !ctxt->group) {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "value found before first group");
+        return -1;
+    }
+
+    keystart = ctxt->cur;
+    while (!IS_EOF && c_isalnum(CUR) && CUR != '=')
+        ctxt->cur++;
+    if (CUR != '=') {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "expected end of value name, expected '='");
+        return -1;
+    }
+
+    if (!(key = strndup(keystart, ctxt->cur - keystart))) {
+        virReportOOMError();
+        return -1;
+    }
+
+    NEXT;
+    valuestart = ctxt->cur;
+    while (!IS_EOF && !IS_EOL(CUR))
+        ctxt->cur++;
+    if (!(IS_EOF || IS_EOL(CUR))) {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "unexpected end of value");
+        goto cleanup;
+    }
+    len = ctxt->cur - valuestart;
+    if (IS_EOF && !IS_EOL(CUR))
+        len++;
+    if (!(value = strndup(valuestart, len))) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    if (virHashAddEntry(ctxt->group, key, value) < 0)
+        goto cleanup;
+
+    NEXT;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(key);
+    return ret;
+}
+
+static int virKeyFileParseComment(virKeyFileParserCtxtPtr ctxt)
+{
+    NEXT;
+
+    while (!IS_EOF && !IS_EOL(CUR))
+        ctxt->cur++;
+
+    NEXT;
+
+    return 0;
+}
+
+static int virKeyFileParseBlank(virKeyFileParserCtxtPtr ctxt)
+{
+    while ((ctxt->cur < ctxt->end) && c_isblank(CUR))
+        ctxt->cur++;
+
+    if (!((ctxt->cur == ctxt->end) || IS_EOL(CUR))) {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "expected newline");
+        return -1;
+    }
+    NEXT;
+    return 0;
+}
+
+static int virKeyFileParseStatement(virKeyFileParserCtxtPtr ctxt)
+{
+    int ret = -1;
+
+    if (CUR == '[') {
+        ret = virKeyFileParseGroup(ctxt);
+    } else if (c_isalnum(CUR)) {
+        ret = virKeyFileParseValue(ctxt);
+    } else if (CUR == '#' || CUR == ';') {
+        ret = virKeyFileParseComment(ctxt);
+    } else if (c_isblank(CUR) || IS_EOL(CUR)) {
+        ret = virKeyFileParseBlank(ctxt);
+    } else {
+        virKeyFileError(ctxt, VIR_ERR_CONF_SYNTAX, "unexpected statement");
+    }
+
+    return ret;
+}
+
+static int virKeyFileParse(virKeyFilePtr conf,
+                           const char *filename,
+                           const char *data,
+                           size_t len)
+{
+    virKeyFileParserCtxt ctxt;
+    int ret = -1;
+
+    VIR_DEBUG("Parse %p '%s' %p %zu", conf, filename, data, len);
+
+    memset(&ctxt, 0, sizeof(ctxt));
+
+    ctxt.filename = filename;
+    ctxt.base = ctxt.cur = data;
+    ctxt.end = data + len - 1;
+    ctxt.line = 1;
+    ctxt.conf = conf;
+
+    while (ctxt.cur < ctxt.end) {
+        if (virKeyFileParseStatement(&ctxt) < 0)
+            goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    VIR_FREE(ctxt.groupname);
+    return ret;
+}
+
+
+static void virKeyFileEntryFree(void *payload, const void *name ATTRIBUTE_UNUSED)
+{
+    virHashFree(payload);
+}
+
+
+virKeyFilePtr virKeyFileNew(void)
+{
+    virKeyFilePtr conf;
+
+    if (VIR_ALLOC(conf) < 0) {
+        virReportOOMError();
+        goto error;
+    }
+
+    if (!(conf->groups = virHashCreate(10,
+                                       virKeyFileEntryFree)))
+        goto error;
+
+    return conf;
+
+error:
+    virKeyFileFree(conf);
+    return NULL;
+}
+
+
+#define MAX_CONFIG_FILE_SIZE (1024 * 1024)
+
+int virKeyFileLoadFile(virKeyFilePtr conf,
+                       const char *filename)
+{
+    char *data = NULL;
+    ssize_t len;
+    int ret;
+
+    if ((len = virFileReadAll(filename, MAX_CONFIG_FILE_SIZE, &data)) < 0)
+        return -1;
+
+    ret = virKeyFileParse(conf, filename, data, len);
+
+    VIR_FREE(data);
+
+    return ret;
+}
+
+
+int virKeyFileLoadData(virKeyFilePtr conf,
+                       const char *path,
+                       const char *data,
+                       size_t len)
+{
+    return virKeyFileParse(conf, path, data, len);
+}
+
+
+void virKeyFileFree(virKeyFilePtr conf)
+{
+    if (!conf)
+        return;
+
+    virHashFree(conf->groups);
+    VIR_FREE(conf);
+}
+
+
+bool virKeyFileHasGroup(virKeyFilePtr conf,
+                       const char *groupname)
+{
+    VIR_DEBUG("conf=%p groupname=%s", conf, groupname);
+    return virHashLookup(conf->groups, groupname) != NULL;
+}
+
+
+bool virKeyFileHasValue(virKeyFilePtr conf,
+                       const char *groupname,
+                       const char *valuename)
+{
+    virHashTablePtr group = virHashLookup(conf->groups, groupname);
+    VIR_DEBUG("conf=%p groupname=%s valuename=%s", conf, groupname, valuename);
+    return group && virHashLookup(group, valuename) != NULL;
+}
+
+const char *virKeyFileGetValueString(virKeyFilePtr conf,
+                                     const char *groupname,
+                                     const char *valuename)
+{
+    virHashTablePtr group = virHashLookup(conf->groups, groupname);
+    VIR_DEBUG("conf=%p groupname=%s valuename=%s", conf, groupname, valuename);
+    return virHashLookup(group, valuename);
+}
diff --git a/src/util/virkeyfile.h b/src/util/virkeyfile.h
new file mode 100644
index 0000000..098ef59
--- /dev/null
+++ b/src/util/virkeyfile.h
@@ -0,0 +1,64 @@
+/*
+ * virkeyfile.h: "ini"-style configuration file handling
+ *
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Authors:
+ *     Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#ifndef __VIR_KEYFILE_H__
+# define __VIR_KEYFILE_H__
+
+# include "internal.h"
+
+/**
+ * virKeyFilePtr:
+ * a pointer to a parsed configuration file
+ */
+typedef struct _virKeyFile virKeyFile;
+typedef virKeyFile *virKeyFilePtr;
+
+virKeyFilePtr virKeyFileNew(void);
+
+int virKeyFileLoadFile(virKeyFilePtr conf,
+                       const char *filename)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
+
+int virKeyFileLoadData(virKeyFilePtr conf,
+                       const char *filename,
+                       const char *data,
+                       size_t len)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
+
+void virKeyFileFree(virKeyFilePtr conf);
+
+bool virKeyFileHasGroup(virKeyFilePtr conf,
+                        const char *groupname)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+bool virKeyFileHasValue(virKeyFilePtr conf,
+                        const char *groupname,
+                        const char *valuename)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+const char *virKeyFileGetValueString(virKeyFilePtr conf,
+                                     const char *groupname,
+                                     const char *valuename)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+#endif /* __VIR_KEYFILE_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 035c8c6..4dde3e9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -96,7 +96,7 @@ check_PROGRAMS = virshtest conftest sockettest \
 	commandtest commandhelper seclabeltest \
 	virhashtest virnetmessagetest virnetsockettest ssh \
 	utiltest virnettlscontexttest shunloadtest \
-	virtimetest viruritest
+	virtimetest viruritest virkeyfiletest
 
 check_LTLIBRARIES = libshunload.la
 
@@ -220,6 +220,7 @@ TESTS = virshtest \
 	virnettlscontexttest \
 	virtimetest \
         viruritest \
+	virkeyfiletest \
 	shunloadtest \
 	utiltest \
 	$(test_scripts)
@@ -512,6 +513,11 @@ viruritest_SOURCES = \
 viruritest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS)
 viruritest_LDADD = ../src/libvirt-net-rpc.la $(LDADDS)
 
+virkeyfiletest_SOURCES = \
+	virkeyfiletest.c testutils.h testutils.c
+virkeyfiletest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS)
+virkeyfiletest_LDADD = ../src/libvirt-net-rpc.la $(LDADDS)
+
 seclabeltest_SOURCES = \
 	seclabeltest.c
 seclabeltest_LDADD = ../src/libvirt_driver_security.la $(LDADDS)
diff --git a/tests/virkeyfiletest.c b/tests/virkeyfiletest.c
new file mode 100644
index 0000000..34dd267
--- /dev/null
+++ b/tests/virkeyfiletest.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This 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 2.1 of the License, or (at your option) any later version.
+ *
+ * This 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#include "testutils.h"
+#include "util.h"
+#include "virterror_internal.h"
+#include "memory.h"
+#include "logging.h"
+
+#include "virkeyfile.h"
+
+#define VIR_FROM_THIS VIR_FROM_RPC
+
+
+static int testParse(const void *args ATTRIBUTE_UNUSED)
+{
+    static const char *cfg1 =
+        "# Some config\n"
+        "\n"
+        "# The first group\n"
+        "[Foo]\n"
+        "one=The first entry is here\n"
+        "two=The second entry\n"
+        "  \n"
+        "three=The third entry\n"
+        "[Bar]\n"
+        "; Another comment\n"
+        "one=The first entry in second group";
+    virKeyFilePtr kf = virKeyFileNew();
+    int ret = -1;
+
+    if (virKeyFileLoadData(kf, "demo.conf", cfg1, strlen(cfg1)) < 0)
+        goto cleanup;
+
+    if (!virKeyFileHasGroup(kf, "Foo")) {
+        VIR_DEBUG("Missing group 'Foo'");
+        goto cleanup;
+    }
+    if (!virKeyFileHasValue(kf, "Foo", "one")) {
+        VIR_DEBUG("Missing Value 'Foo.one'");
+        goto cleanup;
+    }
+    if (!virKeyFileHasValue(kf, "Foo", "two")) {
+        VIR_DEBUG("Missing Value 'Foo.two'");
+        goto cleanup;
+    }
+    if (!virKeyFileHasValue(kf, "Foo", "three")) {
+        VIR_DEBUG("Missing Value 'Foo.three'");
+        goto cleanup;
+    }
+    if (!STREQ(virKeyFileGetValueString(kf, "Foo", "one"),
+               "The first entry is here")) {
+        VIR_DEBUG("Wrong value for 'Foo.one'");
+        goto cleanup;
+    }
+    if (!STREQ(virKeyFileGetValueString(kf, "Foo", "two"),
+               "The second entry")) {
+        VIR_DEBUG("Wrong value for 'Foo.one'");
+        goto cleanup;
+    }
+    if (!STREQ(virKeyFileGetValueString(kf, "Foo", "three"),
+               "The third entry")) {
+        VIR_DEBUG("Wrong value for 'Foo.one'");
+        goto cleanup;
+    }
+
+    if (!virKeyFileHasGroup(kf, "Bar")) {
+        VIR_DEBUG("Missing group 'Bar'");
+        goto cleanup;
+    }
+    if (!virKeyFileHasValue(kf, "Bar", "one")) {
+        VIR_DEBUG("Missing Value 'Bar.one'");
+        goto cleanup;
+    }
+    if (!STREQ(virKeyFileGetValueString(kf, "Bar", "one"),
+               "The first entry in second group")) {
+        VIR_DEBUG("Wrong value for 'Bar.one'");
+        goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    virKeyFileFree(kf);
+    return ret;
+}
+
+
+static int
+mymain(void)
+{
+    int ret = 0;
+
+    signal(SIGPIPE, SIG_IGN);
+
+    if (virtTestRun("Test parse", 1, testParse, NULL) < 0)
+        ret = -1;
+
+    return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+VIRT_TEST_MAIN(mymain)
-- 
1.7.7.6




More information about the libvir-list mailing list