From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> 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@xxxxxxxxxx> + */ + +#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@xxxxxxxxxx> + */ + +#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@xxxxxxxxxx> + */ + +#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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list