/*
   Configuration file handling on top of tini

   Copyright (C) Amitay Isaacs  2017

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

#include "replace.h"
#include "system/locale.h"

#include <talloc.h>

#include "lib/util/dlinklist.h"
#include "lib/util/tini.h"
#include "lib/util/debug.h"

#include "conf/conf.h"

struct conf_value {
	enum conf_type type;
	union {
		const char *string;
		int integer;
		bool boolean;
	} data;
};

union conf_pointer {
	const char **string;
	int *integer;
	bool *boolean;
};

struct conf_option {
	struct conf_option *prev, *next;

	const char *name;
	enum conf_type type;
	void *validate;

	struct conf_value default_value;
	bool default_set;

	struct conf_value *value, *new_value;
	union conf_pointer ptr;
	bool temporary_modified;
};

struct conf_section {
	struct conf_section *prev, *next;

	const char *name;
	conf_validate_section_fn validate;
	struct conf_option *option;
};

struct conf_context {
	const char *filename;
	struct conf_section *section;
	bool define_failed;
	bool ignore_unknown;
	bool reload;
	bool validation_active;
};

/*
 * Functions related to conf_value
 */

static int string_to_string(TALLOC_CTX *mem_ctx,
			    const char *str,
			    const char **str_val)
{
	char *t;

	if (str == NULL) {
		return EINVAL;
	}

	t = talloc_strdup(mem_ctx, str);
	if (t == NULL) {
		return ENOMEM;
	}

	*str_val = t;
	return 0;
}

static int string_to_integer(const char *str, int *int_val)
{
	long t;
	char *endptr = NULL;

	if (str == NULL) {
		return EINVAL;
	}

	t = strtol(str, &endptr, 0);
	if (*str != '\0' || endptr == NULL) {
		if (t < 0 || t > INT_MAX) {
			return EINVAL;
		}

		*int_val = (int)t;
		return 0;
	}

	return EINVAL;
}

static int string_to_boolean(const char *str, bool *bool_val)
{
	if (strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0) {
		*bool_val = true;
		return 0;
	}

	if (strcasecmp(str, "false") == 0 || strcasecmp(str, "no") == 0) {
		*bool_val = false;
		return 0;
	}

	return EINVAL;
}

static int conf_value_from_string(TALLOC_CTX *mem_ctx,
				  const char *str,
				  struct conf_value *value)
{
	int ret;

	switch (value->type) {
	case CONF_STRING:
		ret = string_to_string(mem_ctx, str, &value->data.string);
		break;

	case CONF_INTEGER:
		ret = string_to_integer(str, &value->data.integer);
		break;

	case CONF_BOOLEAN:
		ret = string_to_boolean(str, &value->data.boolean);
		break;

	default:
		return EINVAL;
	}

	return ret;
}

static bool conf_value_compare(struct conf_value *old, struct conf_value *new)
{
	if (old == NULL || new == NULL) {
		return false;
	}

	if (old->type != new->type) {
		return false;
	}

	switch (old->type) {
	case CONF_STRING:
		if (old->data.string == NULL && new->data.string == NULL) {
			return true;
		}
		if (old->data.string != NULL && new->data.string != NULL) {
			if (strcmp(old->data.string, new->data.string) == 0) {
				return true;
			}
		}
		break;

	case CONF_INTEGER:
		if (old->data.integer == new->data.integer) {
			return true;
		}
		break;

	case CONF_BOOLEAN:
		if (old->data.boolean == new->data.boolean) {
			return true;
		}
		break;
	}

	return false;
}

static int conf_value_copy(TALLOC_CTX *mem_ctx,
			   struct conf_value *src,
			   struct conf_value *dst)
{
	if (src->type != dst->type) {
		return EINVAL;
	}

	switch (src->type) {
	case CONF_STRING:
		if (dst->data.string != NULL) {
			talloc_free(discard_const(dst->data.string));
		}
		if (src->data.string == NULL) {
			dst->data.string = NULL;
		} else {
			dst->data.string = talloc_strdup(
				mem_ctx, src->data.string);
			if (dst->data.string == NULL) {
				return ENOMEM;
			}
		}
		break;

	case CONF_INTEGER:
		dst->data.integer = src->data.integer;
		break;

	case CONF_BOOLEAN:
		dst->data.boolean = src->data.boolean;
		break;

	default:
		return EINVAL;
	}

	return 0;
}

static void conf_value_dump(const char *key,
			    struct conf_value *value,
			    bool is_default,
			    bool is_temporary,
			    FILE *fp)
{
	if ((value->type == CONF_STRING && value->data.string == NULL) ||
	    is_default) {
		fprintf(fp, "\t# %s = ", key);
	} else {
		fprintf(fp, "\t%s = ", key);
	}

	switch (value->type) {
	case CONF_STRING:
		if (value->data.string != NULL) {
			fprintf(fp, "%s", value->data.string);
		}
		break;

	case CONF_INTEGER:
		fprintf(fp, "%d", value->data.integer);
		break;

	case CONF_BOOLEAN:
		fprintf(fp, "%s", (value->data.boolean ? "true" : "false"));
		break;
	}

	if (is_temporary) {
		fprintf(fp, " # temporary");
	}

	fprintf(fp, "\n");
}

/*
 * Functions related to conf_option
 */

static struct conf_option *conf_option_find(struct conf_section *s,
					    const char *key)
{
	struct conf_option *opt;

	for (opt = s->option; opt != NULL; opt = opt->next) {
		if (strcmp(opt->name, key) == 0) {
			return opt;
		}
	}

	return NULL;
}

static void conf_option_set_ptr_value(struct conf_option *opt)
{
	switch (opt->type) {
	case CONF_STRING:
		if (opt->ptr.string != NULL) {
			*(opt->ptr.string) = opt->value->data.string;
		}
		break;

	case CONF_INTEGER:
		if (opt->ptr.integer != NULL) {
			*(opt->ptr.integer) = opt->value->data.integer;
		}
		break;

	case CONF_BOOLEAN:
		if (opt->ptr.boolean != NULL) {
			*(opt->ptr.boolean) = opt->value->data.boolean;
		}
		break;
	}
}

static void conf_option_default(struct conf_option *opt);

static int conf_option_add(struct conf_section *s,
			   const char *key,
			   enum conf_type type,
			   void *validate,
			   struct conf_option **popt)
{
	struct conf_option *opt;

	opt = conf_option_find(s, key);
	if (opt != NULL) {
		D_ERR("conf: option \"%s\" already exists\n", key);
		return EEXIST;
	}

	opt = talloc_zero(s, struct conf_option);
	if (opt == NULL) {
		return ENOMEM;
	}

	opt->name = talloc_strdup(opt, key);
	if (opt->name == NULL) {
		talloc_free(opt);
		return ENOMEM;
	}

	opt->type = type;
	opt->validate = validate;

	DLIST_ADD_END(s->option, opt);

	if (popt != NULL) {
		*popt = opt;
	}

	return 0;
}

static int conf_option_set_default(struct conf_option *opt,
				   struct conf_value *default_value)
{
	int ret;

	opt->default_value.type = opt->type;

	ret = conf_value_copy(opt, default_value, &opt->default_value);
	if (ret != 0) {
		return ret;
	}

	opt->default_set = true;
	opt->temporary_modified = false;

	return 0;
}

static void conf_option_set_ptr(struct conf_option *opt,
				union conf_pointer *ptr)
{
	opt->ptr = *ptr;
}

static bool conf_option_validate_string(struct conf_option *opt,
					struct conf_value *value,
					enum conf_update_mode mode)
{
	conf_validate_string_option_fn validate =
		(conf_validate_string_option_fn)opt->validate;

	return validate(opt->name,
			opt->value->data.string,
			value->data.string,
			mode);
}

static bool conf_option_validate_integer(struct conf_option *opt,
					 struct conf_value *value,
					 enum conf_update_mode mode)
{
	conf_validate_integer_option_fn validate =
		(conf_validate_integer_option_fn)opt->validate;

	return validate(opt->name,
			opt->value->data.integer,
			value->data.integer,
			mode);
}

static bool conf_option_validate_boolean(struct conf_option *opt,
					 struct conf_value *value,
					 enum conf_update_mode mode)
{
	conf_validate_boolean_option_fn validate =
		(conf_validate_boolean_option_fn)opt->validate;

	return validate(opt->name,
			opt->value->data.boolean,
			value->data.boolean,
			mode);
}

static bool conf_option_validate(struct conf_option *opt,
				 struct conf_value *value,
				 enum conf_update_mode mode)
{
	int ret;

	if (opt->validate == NULL) {
		return true;
	}

	switch (opt->type) {
	case CONF_STRING:
		ret = conf_option_validate_string(opt, value, mode);
		break;

	case CONF_INTEGER:
		ret = conf_option_validate_integer(opt, value, mode);
		break;

	case CONF_BOOLEAN:
		ret = conf_option_validate_boolean(opt, value, mode);
		break;

	default:
		ret = EINVAL;
	}

	return ret;
}

static bool conf_option_same_value(struct conf_option *opt,
				   struct conf_value *new_value)
{
	return conf_value_compare(opt->value, new_value);
}

static int conf_option_new_value(struct conf_option *opt,
				 struct conf_value *new_value,
				 enum conf_update_mode mode)
{
	int ret;
	bool ok;

	if (opt->new_value != &opt->default_value) {
		TALLOC_FREE(opt->new_value);
	}

	if (new_value == &opt->default_value) {
		/*
		 * This happens only during load/reload. Set the value to
		 * default value, so if the config option is dropped from
		 * config file, then it gets reset to default.
		 */
		opt->new_value = &opt->default_value;
	} else {
		ok = conf_option_validate(opt, new_value, mode);
		if (!ok) {
			D_ERR("conf: validation for option \"%s\" failed\n",
			      opt->name);
			return EINVAL;
		}

		opt->new_value = talloc_zero(opt, struct conf_value);
		if (opt->new_value == NULL) {
			return ENOMEM;
		}

		opt->new_value->type = opt->value->type;
		ret = conf_value_copy(opt, new_value, opt->new_value);
		if (ret != 0) {
			return ret;
		}
	}

	conf_option_set_ptr_value(opt);

	if (new_value != &opt->default_value) {
		if (mode == CONF_MODE_API) {
			opt->temporary_modified = true;
		} else {
			opt->temporary_modified = false;
		}
	}

	return 0;
}

static int conf_option_new_default_value(struct conf_option *opt,
					 enum conf_update_mode mode)
{
	return conf_option_new_value(opt, &opt->default_value, mode);
}

static void conf_option_default(struct conf_option *opt)
{
	if (! opt->default_set) {
		return;
	}

	if (opt->value != &opt->default_value) {
		TALLOC_FREE(opt->value);
	}

	opt->value = &opt->default_value;
	conf_option_set_ptr_value(opt);
}

static void conf_option_reset(struct conf_option *opt)
{
	if (opt->new_value != &opt->default_value) {
		TALLOC_FREE(opt->new_value);
	}

	conf_option_set_ptr_value(opt);
}

static void conf_option_update(struct conf_option *opt)
{
	if (opt->new_value == NULL) {
		return;
	}

	if (opt->value != &opt->default_value) {
		TALLOC_FREE(opt->value);
	}

	opt->value = opt->new_value;
	opt->new_value = NULL;

	conf_option_set_ptr_value(opt);
}

static void conf_option_reset_temporary(struct conf_option *opt)
{
	opt->temporary_modified = false;
}

static bool conf_option_is_default(struct conf_option *opt)
{
	return (opt->value == &opt->default_value);
}

static void conf_option_dump(struct conf_option *opt, FILE *fp)
{
	bool is_default;

	is_default = conf_option_is_default(opt);

	conf_value_dump(opt->name,
			opt->value,
			is_default,
			opt->temporary_modified,
			fp);
}

/*
 * Functions related to conf_section
 */

static struct conf_section *conf_section_find(struct conf_context *conf,
					      const char *section)
{
	struct conf_section *s;

	for (s = conf->section; s != NULL; s = s->next) {
		if (strcasecmp(s->name, section) == 0) {
			return s;
		}
	}

	return NULL;
}

static int conf_section_add(struct conf_context *conf,
			    const char *section,
			    conf_validate_section_fn validate)
{
	struct conf_section *s;

	s = conf_section_find(conf, section);
	if (s != NULL) {
		return EEXIST;
	}

	s = talloc_zero(conf, struct conf_section);
	if (s == NULL) {
		return ENOMEM;
	}

	s->name = talloc_strdup(s, section);
	if (s->name == NULL) {
		talloc_free(s);
		return ENOMEM;
	}

	s->validate = validate;

	DLIST_ADD_END(conf->section, s);
	return 0;
}

static bool conf_section_validate(struct conf_context *conf,
				  struct conf_section *s,
				  enum conf_update_mode mode)
{
	bool ok;

	if (s->validate == NULL) {
		return true;
	}

	ok = s->validate(conf, s->name, mode);
	if (!ok) {
		D_ERR("conf: validation for section [%s] failed\n", s->name);
	}

	return ok;
}

static void conf_section_dump(struct conf_section *s, FILE *fp)
{
	fprintf(fp, "[%s]\n", s->name);
}

/*
 * Functions related to conf_context
 */

static void conf_all_default(struct conf_context *conf)
{
	struct conf_section *s;
	struct conf_option *opt;

	for (s = conf->section; s != NULL; s = s->next) {
		for (opt = s->option; opt != NULL; opt = opt->next) {
			conf_option_default(opt);
		}
	}
}

static int conf_all_temporary_default(struct conf_context *conf,
				      enum conf_update_mode mode)
{
	struct conf_section *s;
	struct conf_option *opt;
	int ret;

	for (s = conf->section; s != NULL; s = s->next) {
		for (opt = s->option; opt != NULL; opt = opt->next) {
			ret = conf_option_new_default_value(opt, mode);
			if (ret != 0) {
				return ret;
			}
		}
	}

	return 0;
}

static void conf_all_reset(struct conf_context *conf)
{
	struct conf_section *s;
	struct conf_option *opt;

	for (s = conf->section; s != NULL; s = s->next) {
		for (opt = s->option; opt != NULL; opt = opt->next) {
			conf_option_reset(opt);
		}
	}
}

static void conf_all_update(struct conf_context *conf)
{
	struct conf_section *s;
	struct conf_option *opt;

	for (s = conf->section; s != NULL; s = s->next) {
		for (opt = s->option; opt != NULL; opt = opt->next) {
			conf_option_update(opt);
			conf_option_reset_temporary(opt);
		}
	}
}

/*
 * API functions
 */

int conf_init(TALLOC_CTX *mem_ctx, struct conf_context **result)
{
	struct conf_context *conf;

	conf = talloc_zero(mem_ctx, struct conf_context);
	if (conf == NULL) {
		return ENOMEM;
	}

	conf->define_failed = false;

	*result = conf;
	return 0;
}

void conf_define_section(struct conf_context *conf,
			 const char *section,
			 conf_validate_section_fn validate)
{
	int ret;

	if (conf->define_failed) {
		return;
	}

	if (section == NULL) {
		conf->define_failed = true;
		return;
	}

	ret = conf_section_add(conf, section, validate);
	if (ret != 0) {
		conf->define_failed = true;
		return;
	}
}

static struct conf_option *conf_define(struct conf_context *conf,
				       const char *section,
				       const char *key,
				       enum conf_type type,
				       conf_validate_string_option_fn validate)
{
	struct conf_section *s;
	struct conf_option *opt;
	int ret;

	s = conf_section_find(conf, section);
	if (s == NULL) {
		D_ERR("conf: unknown section [%s]\n", section);
		return NULL;
	}

	if (key == NULL) {
		D_ERR("conf: option name null in section [%s]\n", section);
		return NULL;
	}

	ret = conf_option_add(s, key, type, validate, &opt);
	if (ret != 0) {
		return NULL;
	}

	return opt;
}

static void conf_define_post(struct conf_context *conf,
			     struct conf_option *opt,
			     struct conf_value *default_value)
{
	int ret;

	ret = conf_option_set_default(opt, default_value);
	if (ret != 0) {
		conf->define_failed = true;
		return;
	}

	conf_option_default(opt);
}

void conf_define_string(struct conf_context *conf,
			const char *section,
			const char *key,
			const char *default_str_val,
			conf_validate_string_option_fn validate)
{
	struct conf_option *opt;
	struct conf_value default_value;

	if (! conf_valid(conf)) {
		return;
	}

	opt = conf_define(conf, section, key, CONF_STRING, validate);
	if (opt == NULL) {
		conf->define_failed = true;
		return;
	}

	default_value.type = CONF_STRING;
	default_value.data.string = default_str_val;

	conf_define_post(conf, opt, &default_value);
}

void conf_define_integer(struct conf_context *conf,
			 const char *section,
			 const char *key,
			 const int default_int_val,
			 conf_validate_integer_option_fn validate)
{
	struct conf_option *opt;
	struct conf_value default_value;

	if (! conf_valid(conf)) {
		return;
	}

	opt = conf_define(conf, section, key, CONF_INTEGER, (void *)validate);
	if (opt == NULL) {
		conf->define_failed = true;
		return;
	}

	default_value.type = CONF_INTEGER;
	default_value.data.integer = default_int_val;

	conf_define_post(conf, opt, &default_value);
}


void conf_define_boolean(struct conf_context *conf,
			 const char *section,
			 const char *key,
			 const bool default_bool_val,
			 conf_validate_boolean_option_fn validate)
{
	struct conf_option *opt;
	struct conf_value default_value;

	if (! conf_valid(conf)) {
		return;
	}

	opt = conf_define(conf, section, key, CONF_BOOLEAN, (void *)validate);
	if (opt == NULL) {
		conf->define_failed = true;
		return;
	}

	default_value.type = CONF_BOOLEAN;
	default_value.data.boolean = default_bool_val;

	conf_define_post(conf, opt, &default_value);
}

static struct conf_option *_conf_option(struct conf_context *conf,
					const char *section,
					const char *key)
{
	struct conf_section *s;
	struct conf_option *opt;

	s = conf_section_find(conf, section);
	if (s == NULL) {
		return NULL;
	}

	opt = conf_option_find(s, key);
	return opt;
}

void conf_assign_string_pointer(struct conf_context *conf,
				const char *section,
				const char *key,
				const char **str_ptr)
{
	struct conf_option *opt;
	union conf_pointer ptr;

	opt = _conf_option(conf, section, key);
	if (opt == NULL) {
		D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
		conf->define_failed = true;
		return;
	}

	if (opt->type != CONF_STRING) {
		conf->define_failed = true;
		return;
	}

	ptr.string = str_ptr;
	conf_option_set_ptr(opt, &ptr);
	conf_option_set_ptr_value(opt);
}

void conf_assign_integer_pointer(struct conf_context *conf,
				 const char *section,
				 const char *key,
				 int *int_ptr)
{
	struct conf_option *opt;
	union conf_pointer ptr;

	opt = _conf_option(conf, section, key);
	if (opt == NULL) {
		D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
		conf->define_failed = true;
		return;
	}

	if (opt->type != CONF_INTEGER) {
		conf->define_failed = true;
		return;
	}

	ptr.integer = int_ptr;
	conf_option_set_ptr(opt, &ptr);
	conf_option_set_ptr_value(opt);
}

void conf_assign_boolean_pointer(struct conf_context *conf,
				 const char *section,
				 const char *key,
				 bool *bool_ptr)
{
	struct conf_option *opt;
	union conf_pointer ptr;

	opt = _conf_option(conf, section, key);
	if (opt == NULL) {
		D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
		conf->define_failed = true;
		return;
	}

	if (opt->type != CONF_BOOLEAN) {
		conf->define_failed = true;
		return;
	}

	ptr.boolean = bool_ptr;
	conf_option_set_ptr(opt, &ptr);
	conf_option_set_ptr_value(opt);
}

bool conf_query(struct conf_context *conf,
		const char *section,
		const char *key,
		enum conf_type *type)
{
	struct conf_section *s;
	struct conf_option *opt;

	if (! conf_valid(conf)) {
		return false;
	}

	s = conf_section_find(conf, section);
	if (s == NULL) {
		return false;
	}

	opt = conf_option_find(s, key);
	if (opt == NULL) {
		return false;
	}

	if (type != NULL) {
		*type = opt->type;
	}
	return true;
}

bool conf_valid(struct conf_context *conf)
{
	if (conf->define_failed) {
		return false;
	}

	return true;
}

void conf_set_defaults(struct conf_context *conf)
{
	conf_all_default(conf);
}

struct conf_load_state {
	struct conf_context *conf;
	struct conf_section *s;
	enum conf_update_mode mode;
	int err;
};

static bool conf_load_section(const char *section, void *private_data);
static bool conf_load_option(const char *name,
			     const char *value_str,
			     void *private_data);

static int conf_load_internal(struct conf_context *conf)
{
	struct conf_load_state state;
	FILE *fp;
	int ret;
	bool ok;

	state = (struct conf_load_state) {
		.conf = conf,
		.mode = (conf->reload ? CONF_MODE_RELOAD : CONF_MODE_LOAD),
	};

	ret = conf_all_temporary_default(conf, state.mode);
	if (ret != 0) {
		return ret;
	}

	fp = fopen(conf->filename, "r");
	if (fp == NULL) {
		return errno;
	}

	ok = tini_parse(fp,
			false,
			conf_load_section,
			conf_load_option,
			&state);
	fclose(fp);
	if (!ok) {
		goto fail;
	}

	/* Process the last section */
	if (state.s != NULL) {
		ok = conf_section_validate(conf, state.s, state.mode);
		if (!ok) {
			state.err = EINVAL;
			goto fail;
		}
	}

	if (state.err != 0) {
		goto fail;
	}

	conf_all_update(conf);
	return 0;

fail:
	conf_all_reset(conf);
	return state.err;
}

static bool conf_load_section(const char *section, void *private_data)
{
	struct conf_load_state *state =
		(struct conf_load_state *)private_data;
	bool ok;

	if (state->s != NULL) {
		ok = conf_section_validate(state->conf, state->s, state->mode);
		if (!ok) {
			state->err = EINVAL;
			return true;
		}
	}

	state->s = conf_section_find(state->conf, section);
	if (state->s == NULL) {
		if (state->conf->ignore_unknown) {
			D_DEBUG("conf: ignoring unknown section [%s]\n",
				section);
		} else {
			D_ERR("conf: unknown section [%s]\n", section);
			state->err = EINVAL;
			return true;
		}
	}

	return true;
}

static bool conf_load_option(const char *name,
			     const char *value_str,
			     void *private_data)
{
	struct conf_load_state *state =
		(struct conf_load_state *)private_data;
	struct conf_option *opt;
	TALLOC_CTX *tmp_ctx;
	struct conf_value value;
	int ret;
	bool ok;

	if (state->s == NULL) {
		if (state->conf->ignore_unknown) {
			D_DEBUG("conf: unknown section for option \"%s\"\n",
				name);
			return true;
		} else {
			D_ERR("conf: unknown section for option \"%s\"\n",
			      name);
			state->err = EINVAL;
			return true;
		}
	}

	opt = conf_option_find(state->s, name);
	if (opt == NULL) {
		if (state->conf->ignore_unknown) {
			D_DEBUG("conf: unknown option [%s] -> \"%s\"\n",
				state->s->name,
				name);
			return true;
		} else {
			D_ERR("conf: unknown option [%s] -> \"%s\"\n",
			      state->s->name,
			      name);
			state->err = EINVAL;
			return true;
		}
	}

	if (strlen(value_str) == 0) {
		D_ERR("conf: empty value [%s] -> \"%s\"\n",
		      state->s->name,
		      name);
		state->err = EINVAL;
		return true;
	}

	tmp_ctx = talloc_new(state->conf);
	if (tmp_ctx == NULL) {
		state->err = ENOMEM;
		return false;
	}

	value.type = opt->type;
	ret = conf_value_from_string(tmp_ctx, value_str, &value);
	if (ret != 0) {
		D_ERR("conf: invalid value [%s] -> \"%s\" = \"%s\"\n",
		      state->s->name,
		      name,
		      value_str);
		talloc_free(tmp_ctx);
		state->err = ret;
		return true;
	}

	ok = conf_option_same_value(opt, &value);
	if (ok) {
		goto done;
	}

	ret = conf_option_new_value(opt, &value, state->mode);
	if (ret != 0) {
		talloc_free(tmp_ctx);
		state->err = ret;
		return true;
	}

done:
	talloc_free(tmp_ctx);
	return true;

}

int conf_load(struct conf_context *conf,
	      const char *filename,
	      bool ignore_unknown,
	      bool verbose)
{
	conf->filename = talloc_strdup(conf, filename);
	if (conf->filename == NULL) {
		return ENOMEM;
	}

	conf->ignore_unknown = ignore_unknown;

	if (verbose) {
		D_NOTICE("Reading config file %s\n", filename);
	}

	return conf_load_internal(conf);
}

int conf_reload(struct conf_context *conf)
{
	int ret;

	if (conf->filename == NULL) {
		return EPERM;
	}

	D_NOTICE("Re-reading config file %s\n", conf->filename);

	conf->reload = true;
	ret = conf_load_internal(conf);
	conf->reload = false;

	return ret;
}

static int conf_set(struct conf_context *conf,
		    const char *section,
		    const char *key,
		    struct conf_value *value)
{
	struct conf_section *s;
	struct conf_option *opt;
	int ret;
	bool ok;

	s = conf_section_find(conf, section);
	if (s == NULL) {
		return EINVAL;
	}

	opt = conf_option_find(s, key);
	if (opt == NULL) {
		return EINVAL;
	}

	if (opt->type != value->type) {
		return EINVAL;
	}

	ok = conf_option_same_value(opt, value);
	if (ok) {
		return 0;
	}

	ret = conf_option_new_value(opt, value, CONF_MODE_API);
	if (ret != 0) {
		conf_option_reset(opt);
		return ret;
	}

	ok = conf_section_validate(conf, s, CONF_MODE_API);
	if (!ok) {
		conf_option_reset(opt);
		return EINVAL;
	}

	conf_option_update(opt);
	return 0;
}

int conf_set_string(struct conf_context *conf,
		    const char *section,
		    const char *key,
		    const char *str_val)
{
	struct conf_value value;

	value.type = CONF_STRING;
	value.data.string = str_val;

	return conf_set(conf, section, key, &value);
}

int conf_set_integer(struct conf_context *conf,
		     const char *section,
		     const char *key,
		     int int_val)
{
	struct conf_value value;

	value.type = CONF_INTEGER;
	value.data.integer = int_val;

	return conf_set(conf, section, key, &value);
}

int conf_set_boolean(struct conf_context *conf,
		     const char *section,
		     const char *key,
		     bool bool_val)
{
	struct conf_value value;

	value.type = CONF_BOOLEAN;
	value.data.boolean = bool_val;

	return conf_set(conf, section, key, &value);
}

static int conf_get(struct conf_context *conf,
		    const char *section,
		    const char *key,
		    enum conf_type type,
		    const struct conf_value **value,
		    bool *is_default)
{
	struct conf_section *s;
	struct conf_option *opt;

	s = conf_section_find(conf, section);
	if (s == NULL) {
		return EINVAL;
	}

	opt = conf_option_find(s, key);
	if (opt == NULL) {
		return EINVAL;
	}

	if (opt->type != type) {
		return EINVAL;
	}

	*value = opt->value;
	if (is_default != NULL) {
		*is_default = conf_option_is_default(opt);
	}

	return 0;
}

int conf_get_string(struct conf_context *conf,
		    const char *section,
		    const char *key,
		    const char **str_val,
		    bool *is_default)
{
	const struct conf_value *value;
	int ret;

	ret = conf_get(conf, section, key, CONF_STRING, &value, is_default);
	if (ret != 0) {
		return ret;
	}

	*str_val = value->data.string;
	return 0;
}

int conf_get_integer(struct conf_context *conf,
		     const char *section,
		     const char *key,
		     int *int_val,
		     bool *is_default)
{
	const struct conf_value *value;
	int ret;

	ret = conf_get(conf, section, key, CONF_INTEGER, &value, is_default);
	if (ret != 0) {
		return ret;
	}

	*int_val = value->data.integer;
	return 0;
}

int conf_get_boolean(struct conf_context *conf,
		     const char *section,
		     const char *key,
		     bool *bool_val,
		     bool *is_default)
{
	const struct conf_value *value;
	int ret;

	ret = conf_get(conf, section, key, CONF_BOOLEAN, &value, is_default);
	if (ret != 0) {
		return ret;
	}

	*bool_val = value->data.boolean;
	return 0;
}

void conf_dump(struct conf_context *conf, FILE *fp)
{
	struct conf_section *s;
	struct conf_option *opt;

	for (s = conf->section; s != NULL; s = s->next) {
		conf_section_dump(s, fp);
		for (opt = s->option; opt != NULL; opt = opt->next) {
			conf_option_dump(opt, fp);
		}
	}
}
