mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 19:01:14 +00:00
09567144d6
I almost wrote a hash value free function that just called VIR_FREE, then realized I couldn't be the first person to do that. Sure enough, it was worth factoring into a common helper routine. * src/util/virhash.h (virHashValueFree): New function. * src/util/virhash.c (virHashValueFree): Implement it. * src/util/virobject.h (virObjectFreeHashData): New function. * src/libvirt_private.syms (virhash.h, virobject.h): Export them. * src/nwfilter/nwfilter_learnipaddr.c (virNWFilterLearnInit): Use common function. * src/qemu/qemu_capabilities.c (virQEMUCapsCacheNew): Likewise. * src/qemu/qemu_command.c (qemuDomainCCWAddressSetCreate): Likewise. * src/qemu/qemu_monitor.c (qemuMonitorGetBlockInfo): Likewise. * src/qemu/qemu_process.c (qemuProcessWaitForMonitor): Likewise. * src/util/virclosecallbacks.c (virCloseCallbacksNew): Likewise. * src/util/virkeyfile.c (virKeyFileParseGroup): Likewise. * tests/qemumonitorjsontest.c (testQemuMonitorJSONqemuMonitorJSONGetBlockInfo): Likewise. Signed-off-by: Eric Blake <eblake@redhat.com>
361 lines
8.5 KiB
C
361 lines
8.5 KiB
C
/*
|
|
* virkeyfile.c: "ini"-style configuration file handling
|
|
*
|
|
* Copyright (C) 2012-2014 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, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Daniel P. Berrange <berrange@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "c-ctype.h"
|
|
#include "virlog.h"
|
|
#include "viralloc.h"
|
|
#include "virfile.h"
|
|
#include "virutil.h"
|
|
#include "virhash.h"
|
|
#include "virkeyfile.h"
|
|
#include "virerror.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_CONF
|
|
|
|
VIR_LOG_INIT("util.keyfile");
|
|
|
|
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 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 (VIR_STRNDUP(ctxt->groupname, name, ctxt->cur - name) < 0)
|
|
return -1;
|
|
|
|
NEXT;
|
|
|
|
if (!(ctxt->group = virHashCreate(10, virHashValueFree)))
|
|
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 (VIR_STRNDUP(key, keystart, ctxt->cur - keystart) < 0)
|
|
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 (VIR_STRNDUP(value, valuestart, len) < 0)
|
|
goto cleanup;
|
|
|
|
if (virHashAddEntry(ctxt->group, key, value) < 0) {
|
|
VIR_FREE(value);
|
|
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)
|
|
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);
|
|
}
|