Skip to content

Commit

Permalink
Merge pull request #139 from martinh/key-value-sections
Browse files Browse the repository at this point in the history
Free-form dynamic key=value sections
  • Loading branch information
troglobit committed Jun 21, 2020
2 parents f1132c7 + 8c23bf0 commit a24c7b1
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 23 deletions.
3 changes: 2 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ AC_CHECK_HEADERS([unistd.h string.h strings.h sys/stat.h windows.h])
AC_C_CONST

# Checks for library functions.
AC_CHECK_FUNCS([fmemopen funopen strcasecmp strdup strndup setenv unsetenv _putenv])
AC_CHECK_FUNCS([fmemopen funopen reallocarray strcasecmp strdup strndup setenv unsetenv _putenv])

# Set conditional includes in Makefile.am
AM_CONDITIONAL(MISSING_FMEMOPEN, [test "x$ac_cv_func_fmemopen" = "xno"])
AM_CONDITIONAL(MISSING_REALLOCARRAY, [test "x$ac_cv_func_reallocarray" = "xno"])
AM_CONDITIONAL(WINDOWS_BUILD, [test "x$ac_cv_header_windows_h" = "xyes"])

# Files to generate
Expand Down
1 change: 1 addition & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ nested
deprecated
addsec
parsebuf
env
4 changes: 2 additions & 2 deletions examples/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
EXTRA_DIST = simple.conf reread.conf ftp.conf test.conf nested.conf deprecated.conf
noinst_PROGRAMS = simple ftpconf cfgtest cli nested deprecated addsec parsebuf
EXTRA_DIST = simple.conf reread.conf ftp.conf test.conf nested.conf deprecated.conf env.conf
noinst_PROGRAMS = simple ftpconf cfgtest cli nested deprecated addsec parsebuf env
AM_CPPFLAGS = -I$(top_srcdir)/src
AM_LDFLAGS = -L../src/
LIBS = $(LTLIBINTL)
Expand Down
55 changes: 55 additions & 0 deletions examples/env.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Example of how an application can allow free-form key=value strings as settings,
* e.g., custom environment variables the program should set for child processes.
*
* env.conf:
* env {
* foo = bar
* baz = foo
* }
*/
#include <err.h>
#include <confuse.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
const char *file = "env.conf";
cfg_opt_t opts[] = {
CFG_SEC("env", NULL, CFGF_KEYSTRVAL),
CFG_END()
};
cfg_t *cfg, *sec;
int rc;

if (argc > 1)
file = argv[1];

cfg = cfg_init(opts, 0);
if (!cfg)
err(1, "Failed cfg_init()");

rc = cfg_parse(cfg, file);
if (rc != CFG_SUCCESS) {
if (rc == CFG_FILE_ERROR)
err(1, "Failed opening %s", file);

errx(1, "Failed parsing %s", file);
}

sec = cfg_getsec(cfg, "env");
if (sec) {
unsigned int i;

for (i = 0; i < cfg_num(sec); i++) {
cfg_opt_t *opt = cfg_getnopt(sec, i);

printf("%s = \"%s\"\n", cfg_opt_name(opt), cfg_opt_getstr(opt));
}
}

// cfg_print(cfg, stdout);
cfg_free(cfg);

return 0;
}
5 changes: 5 additions & 0 deletions examples/env.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
env {
foo = bar
baz = foo
}

3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ CLEANFILES = *~ \#*\#
if MISSING_FMEMOPEN
libconfuse_la_SOURCES += fmemopen.c
endif
if MISSING_REALLOCARRAY
libconfuse_la_SOURCES += reallocarray.c
endif
47 changes: 47 additions & 0 deletions src/confuse.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ static int cfg_print_pff_indent(cfg_t *cfg, FILE *fp,
extern FILE *fmemopen(void *buf, size_t size, const char *type);
#endif

#ifndef HAVE_REALLOCARRAY
extern void *reallocarray(void *optr, size_t nmemb, size_t size);
#endif

#ifndef HAVE_STRDUP
/*
* Copyright (c) 1988, 1993
Expand Down Expand Up @@ -387,6 +391,11 @@ DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt)
return NULL;
}

DLLIMPORT const char *cfg_opt_getstr(cfg_opt_t *opt)
{
return cfg_opt_getnstr(opt, 0);
}

DLLIMPORT unsigned int cfg_opt_size(cfg_opt_t *opt)
{
if (opt)
Expand Down Expand Up @@ -610,6 +619,31 @@ static cfg_value_t *cfg_addval(cfg_opt_t *opt)
return opt->values[opt->nvalues++];
}

static cfg_opt_t *cfg_addopt(cfg_t *cfg, char *key)
{
int num = cfg_num(cfg);
cfg_opt_t *opts;

opts = reallocarray(cfg->opts, num + 2, sizeof(cfg_opt_t));
if (!opts)
return NULL;

/* Write new opt to previous CFG_END() marker */
cfg->opts = opts;
cfg->opts[num].name = strdup(key);
cfg->opts[num].type = CFGT_STR;

if (!cfg->opts[num].name) {
free(opts);
return NULL;
}

/* Set new CFG_END() */
memset(&cfg->opts[num + 1], 0, sizeof(cfg_opt_t));

return &cfg->opts[num];
}

DLLIMPORT int cfg_numopts(cfg_opt_t *opts)
{
int n;
Expand Down Expand Up @@ -984,6 +1018,9 @@ DLLIMPORT cfg_value_t *cfg_setopt(cfg_t *cfg, cfg_opt_t *opt, const char *value)
}

val->section->flags = cfg->flags;
if (is_set(CFGF_KEYSTRVAL, opt->flags))
val->section->flags |= CFGF_KEYSTRVAL;

val->section->filename = cfg->filename ? strdup(cfg->filename) : NULL;
if (cfg->filename && !val->section->filename) {
free(val->section->name);
Expand Down Expand Up @@ -1309,6 +1346,16 @@ static int cfg_parse_internal(cfg_t *cfg, int level, int force_state, cfg_opt_t
break;
}

/* Not found, is it a dynamic key-value section? */
if (is_set(CFGF_KEYSTRVAL, cfg->flags)) {
opt = cfg_addopt(cfg, cfg_yylval);
if (!opt)
goto error;

state = 1;
break;
}

goto error;
}

Expand Down
45 changes: 27 additions & 18 deletions src/confuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,23 @@ enum cfg_type_t {
typedef enum cfg_type_t cfg_type_t;

/** Flags. */
#define CFGF_NONE 0
#define CFGF_MULTI 1 /**< option may be specified multiple times (only applies to sections) */
#define CFGF_LIST 2 /**< option is a list */
#define CFGF_NOCASE 4 /**< configuration file is case insensitive */
#define CFGF_TITLE 8 /**< option has a title (only applies to sections) */
#define CFGF_NODEFAULT 16 /**< option has no default value */
#define CFGF_NO_TITLE_DUPES 32 /**< multiple section titles must be unique
(duplicates raises an error, only applies to
sections) */

#define CFGF_RESET 64
#define CFGF_DEFINIT 128
#define CFGF_IGNORE_UNKNOWN 256 /**< ignore unknown options in configuration files */
#define CFGF_DEPRECATED 512 /**< option is deprecated and should be ignored. */
#define CFGF_DROP 1024 /**< option should be dropped after parsing */
#define CFGF_COMMENTS 2048 /**< Enable option annotation/comments support */
#define CFGF_MODIFIED 4096 /**< option has been changed from its default value */
#define CFGF_NONE (0)
#define CFGF_MULTI (1 << 0) /**< option may be specified multiple times (only applies to sections) */
#define CFGF_LIST (1 << 1) /**< option is a list */
#define CFGF_NOCASE (1 << 2) /**< configuration file is case insensitive */
#define CFGF_TITLE (1 << 3) /**< option has a title (only applies to sections) */
#define CFGF_NODEFAULT (1 << 4) /**< option has no default value */
#define CFGF_NO_TITLE_DUPES (1 << 5) /**< multiple section titles must be unique
(duplicates raises an error, only applies to sections) */

#define CFGF_RESET (1 << 6)
#define CFGF_DEFINIT (1 << 7)
#define CFGF_IGNORE_UNKNOWN (1 << 8) /**< ignore unknown options in configuration files */
#define CFGF_DEPRECATED (1 << 9) /**< option is deprecated and should be ignored. */
#define CFGF_DROP (1 << 10) /**< option should be dropped after parsing */
#define CFGF_COMMENTS (1 << 11) /**< Enable option annotation/comments support */
#define CFGF_MODIFIED (1 << 12) /**< option has been changed from its default value */
#define CFGF_KEYSTRVAL (1 << 13) /**< section has free-form key=value string options created when parsing file */

/** Return codes from cfg_parse(), cfg_parse_boolean(), and cfg_set*() functions. */
#define CFG_SUCCESS 0
Expand Down Expand Up @@ -585,7 +585,7 @@ extern const char __export confuse_author[];
* The options must no longer be defined in the same scope as where the cfg_xxx
* functions are used (since version 2.3).
*
* CFG_IGNORE_UNKNOWN can be specified to use the "__unknown" option
* CFGF_IGNORE_UNKNOWN can be specified to use the "__unknown" option
* whenever an unknown option is parsed. Be sure to define an "__unknown"
* option in each scope that unknown parameters are allowed.
*
Expand Down Expand Up @@ -932,6 +932,15 @@ DLLIMPORT const char *__export cfg_name(cfg_t *cfg);
*/
DLLIMPORT const char *__export cfg_opt_name(cfg_opt_t *opt);

/** Return the string value of a key=value pair
*
* @param opt The option structure (eg, as returned from cfg_getnopt())
* @see cfg_opt_name
* @return The string value for the option, or NULL if it's not a
* string. This string must not be modified!
*/
DLLIMPORT const char *cfg_opt_getstr(cfg_opt_t *opt);

/** Predefined include-function. This function can be used in the
* options passed to cfg_init() to specify a function for including
* other configuration files in the parsing. For example:
Expand Down
38 changes: 38 additions & 0 deletions src/reallocarray.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */
/*
* Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <sys/types.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>

/*
* This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
* if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
*/
#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))

void *
reallocarray(void *optr, size_t nmemb, size_t size)
{
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size) {
errno = ENOMEM;
return NULL;
}
return realloc(optr, size * nmemb);
}
3 changes: 2 additions & 1 deletion tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ check_confuse
empty_string
env
ignore_parm
keyval
list_plus_syntax
malloc.log
modified_flag
Expand All @@ -27,4 +28,4 @@ single_title_sections
searchpath
*.o
*.log
*.trs
*.trs
2 changes: 1 addition & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
EXTRA_DIST = annotate.conf a.conf b.conf spdir check_confuse.h

TESTS =
TESTS = keyval
TESTS += suite_single
TESTS += suite_dup
TESTS += suite_func
Expand Down
91 changes: 91 additions & 0 deletions tests/keyval.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* Test key=value pairs, e.g. env vars. a program should set to children */

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include "check_confuse.h"

static void check_byname(cfg_t *cfg, const char *key, const char *val)
{
char *value;

printf("Checking for key:%s, expected value %s\n", key, val);

value = cfg_getstr(cfg, key);
printf("Found value %s\n", value);

fail_unless(value != NULL);
fail_unless(!strcmp(value, val));
}

static void check_keyval(cfg_t *cfg, unsigned int index, const char *key, const char *val)
{
cfg_opt_t *opt;
int rc;

printf("Checking index %u for key:%s val:%s\n", index, key, val);

opt = cfg_getnopt(cfg, index);
fail_unless(opt != NULL);

printf("Found key:%s val:%s\n", cfg_opt_name(opt), cfg_opt_getstr(opt));

rc =strcmp(cfg_opt_name(opt), key);
fail_unless(rc == 0);

rc =strcmp(cfg_opt_getstr(opt), val);
fail_unless(rc == 0);
}

int main(void)
{
cfg_opt_t opts[] = {
CFG_SEC("env", NULL, CFGF_KEYSTRVAL),
CFG_END()
};
unsigned int num;
cfg_t *cfg, *sec;
cfg_opt_t *opt;
char *key;
int rc;

cfg = cfg_init(opts, CFGF_NONE);
fail_unless(cfg != NULL);

rc = cfg_parse_buf(cfg, "env {\n"
" foo=bar\n"
" bar=for"
" baz=qux\n"
" bar=xyzzy\n" /* Should replace previous 'bar' */
"}");
fail_unless(rc == CFG_SUCCESS);

sec = cfg_getsec(cfg, "env");
fail_unless(sec != NULL);

/* Fuzz internals a bit, check for non-existing key */
cfg_getstr(sec, "some-key-not-in-the-config-file");

num = cfg_num(sec);
fail_unless(num == 3); /* { foo, bar, baz } */

check_keyval(sec, 0, "foo", "bar");
check_keyval(sec, 1, "bar", "xyzzy");
check_keyval(sec, 2, "baz", "qux");

check_byname(sec, "foo", "bar");
check_byname(sec, "baz", "qux");
check_byname(sec, "bar", "xyzzy");

printf("PASS\n");
cfg_free(cfg);

return 0;
}

/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/

0 comments on commit a24c7b1

Please sign in to comment.