view src/lib-settings/settings-parser.c @ 9002:9d0037a997f4 HEAD

Initial commit for config rewrite.
author Timo Sirainen <tss@iki.fi>
date Tue, 27 Jan 2009 18:21:53 -0500
parents
children f5e35bc340bf
line wrap: on
line source

/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "hash.h"
#include "network.h"
#include "istream.h"
#include "str.h"
#include "strescape.h"
#include "var-expand.h"
#include "settings-parser.h"

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>

#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')

struct setting_link {
        struct setting_link *parent;
	const struct setting_parser_info *info;

	ARRAY_TYPE(void_array) *array;
	void *set_struct;
};

struct setting_parser_context {
	pool_t set_pool, parser_pool;
        enum settings_parser_flags flags;
	bool str_vars_are_expanded;

	struct setting_link *roots;
	unsigned int root_count;
	struct hash_table *links;
	string_t *save_input_str;

	unsigned int linenum;
	const char *error;
	const struct setting_parser_info *prev_info;
};

static const struct setting_parser_info strlist_info = {
	MEMBER(defines) NULL,
	MEMBER(defaults) NULL,

	MEMBER(parent) NULL,
	MEMBER(dynamic_parsers) NULL,

	MEMBER(parent_offset) (size_t)-1,
	MEMBER(type_offset) (size_t)-1,
	MEMBER(struct_size) 0
};

struct setting_parser_context *
settings_parser_init(pool_t set_pool, const struct setting_parser_info *root,
		     enum settings_parser_flags flags)
{
        return settings_parser_init_list(set_pool, &root, 1, flags);
}

static void
setting_parser_copy_defaults(const struct setting_parser_info *info,
			     pool_t pool, void *dest)
{
	const struct setting_define *def;
	const char *p, **strp;

	if (info->defaults == NULL)
		return;

	memcpy(dest, info->defaults, info->struct_size);
	for (def = info->defines; def->key != NULL; def++) {
		switch (def->type) {
		case SET_ENUM: {
			/* fix enums by dropping everything after the
			   first ':' */
			strp = STRUCT_MEMBER_P(dest, def->offset);
			p = strchr(*strp, ':');
			if (p != NULL)
				*strp = p_strdup_until(pool, *strp, p);
			break;
		}
		case SET_STR_VARS: {
			/* insert the unexpanded-character */
			strp = STRUCT_MEMBER_P(dest, def->offset);
			if (*strp != NULL) {
				*strp = p_strconcat(pool,
						    SETTING_STRVAR_UNEXPANDED,
						    *strp, NULL);
			}
			break;
		}
		default:
			break;
		}
	}
}

struct setting_parser_context *
settings_parser_init_list(pool_t set_pool,
			  const struct setting_parser_info *const *roots,
			  unsigned int count, enum settings_parser_flags flags)
{
	struct setting_parser_context *ctx;
	unsigned int i;
	pool_t parser_pool;

	i_assert(count > 0);

	parser_pool = pool_alloconly_create("settings parser", 8192);
	ctx = p_new(parser_pool, struct setting_parser_context, 1);
	ctx->set_pool = set_pool;
	ctx->parser_pool = parser_pool;
	ctx->flags = flags;

	ctx->root_count = count;
	ctx->roots = p_new(ctx->set_pool, struct setting_link, count);
	for (i = 0; i < count; i++) {
		ctx->roots[i].info = roots[i];
		ctx->roots[i].set_struct =
			p_malloc(ctx->set_pool, roots[i]->struct_size);
		setting_parser_copy_defaults(roots[i], ctx->set_pool,
					     ctx->roots[i].set_struct);
	}

	ctx->links = hash_table_create(default_pool, ctx->parser_pool, 0,
				       str_hash, (hash_cmp_callback_t *)strcmp);
	pool_ref(ctx->set_pool);
	return ctx;
}

void settings_parser_deinit(struct setting_parser_context **_ctx)
{
	struct setting_parser_context *ctx = *_ctx;

	*_ctx = NULL;
	hash_table_destroy(&ctx->links);
	pool_unref(&ctx->set_pool);
	pool_unref(&ctx->parser_pool);
}

void *settings_parser_get(struct setting_parser_context *ctx)
{
	i_assert(ctx->root_count == 1);

	return ctx->roots[0].set_struct;
}

void **settings_parser_get_list(struct setting_parser_context *ctx)
{
	unsigned int i;
	void **sets;

	sets = t_new(void *, ctx->root_count);
	for (i = 0; i < ctx->root_count; i++)
		sets[i] = ctx->roots[i].set_struct;
	return sets;
}

const char *settings_parser_get_error(struct setting_parser_context *ctx)
{
	return ctx->error;
}

static const struct setting_define *
setting_define_find(const struct setting_parser_info *info, const char *key)
{
	const struct setting_define *list;

	for (list = info->defines; list->key != NULL; list++) {
		if (strcmp(list->key, key) == 0 && list->type != SET_INTERNAL)
			return list;
	}
	return NULL;
}

static int
get_bool(struct setting_parser_context *ctx, const char *value, bool *result_r)
{
	/* FIXME: eventually we'd want to support only yes/no */
	if (strcasecmp(value, "yes") == 0 ||
	    strcasecmp(value, "y") == 0 || strcmp(value, "1") == 0)
		*result_r = TRUE;
	else if (strcasecmp(value, "no") == 0)
		*result_r = FALSE;
	else {
		ctx->error = p_strconcat(ctx->parser_pool, "Invalid boolean: ",
					 value, NULL);
		return -1;
	}

	return 0;
}

static int
get_uint(struct setting_parser_context *ctx, const char *value,
	 unsigned int *result_r)
{
	int num;

	/* use %i so we can handle eg. 0600 as octal value with umasks */
	if (!sscanf(value, "%i", &num) || num < 0) {
		ctx->error = p_strconcat(ctx->parser_pool, "Invalid number: ",
					 value, NULL);
		return -1;
	}
	*result_r = num;
	return 0;
}

static int get_enum(struct setting_parser_context *ctx, const char *value,
		    char **result_r, const char *allowed_values)
{
	const char *p;

	while (allowed_values != NULL) {
		p = strchr(allowed_values, ':');
		if (p == NULL) {
			if (strcmp(allowed_values, value) == 0)
				break;

			ctx->error = p_strconcat(ctx->parser_pool,
						 "Invalid value: ",
						 value, NULL);
			return -1;
		}

		if (strncmp(allowed_values, value, p - allowed_values) == 0 &&
		    value[p - allowed_values] == '\0')
			break;

		allowed_values = p + 1;
	}

	*result_r = p_strdup(ctx->set_pool, value);
	return 0;
}

static int
get_deflist(struct setting_parser_context *ctx, struct setting_link *parent,
	    const struct setting_parser_info *info,
	    const char *key, const char *value, ARRAY_TYPE(void_array) *result)
{
	struct setting_link *link;
	const char *const *list;
	char *full_key;

	i_assert(info->defines != NULL || info == &strlist_info);

	if (!array_is_created(result))
		p_array_init(result, ctx->set_pool, 5);

	list = t_strsplit(value, "\t ");
	for (; *list != NULL; list++) {
		if (**list == '\0')
			continue;

		full_key = p_strconcat(ctx->parser_pool, key,
				       SETTINGS_SEPARATOR_S, *list, NULL);
		if (hash_table_lookup(ctx->links, full_key) != NULL) {
			ctx->error = p_strconcat(ctx->parser_pool, full_key,
						 " already exists", NULL);
			return -1;
		}

		link = p_new(ctx->parser_pool, struct setting_link, 1);
		link->parent = parent;
		link->info = info;
		link->array = result;
		hash_table_insert(ctx->links, full_key, link);
	}
	return 0;
}

static int
settings_parse(struct setting_parser_context *ctx, struct setting_link *link,
	       const struct setting_define *def,
	       const char *key, const char *value)
{
        void *ptr, *ptr2;

	ctx->prev_info = link->info;

	if (link->set_struct == NULL) {
		link->set_struct =
			p_malloc(ctx->set_pool, link->info->struct_size);
		setting_parser_copy_defaults(link->info, ctx->set_pool,
					     link->set_struct);
		array_append(link->array, &link->set_struct, 1);

		if (link->info->parent_offset != (size_t)-1 &&
		    link->parent != NULL) {
			ptr = STRUCT_MEMBER_P(link->set_struct,
					      link->info->parent_offset);
			*((void **)ptr) = link->parent->set_struct;
		}
	}

	ptr = STRUCT_MEMBER_P(link->set_struct, def->offset);
	switch (def->type) {
	case SET_INTERNAL:
		i_unreached();
	case SET_BOOL:
		return get_bool(ctx, value, (bool *)ptr);
	case SET_UINT:
		return get_uint(ctx, value, (unsigned int *)ptr);
	case SET_STR:
		*((char **)ptr) = p_strdup(ctx->set_pool, value);
		return 0;
	case SET_STR_VARS:
		*((char **)ptr) = p_strconcat(ctx->set_pool,
					      ctx->str_vars_are_expanded ?
					      SETTING_STRVAR_EXPANDED :
					      SETTING_STRVAR_UNEXPANDED,
					      value, NULL);
		return 0;
	case SET_ENUM:
		/* get the available values from default string */
		i_assert(link->info->defaults != NULL);
		ptr2 = STRUCT_MEMBER_P(link->info->defaults, def->offset);
		return get_enum(ctx, value, (char **)ptr, *(const char **)ptr2);
	case SET_DEFLIST:
		ctx->prev_info = def->list_info;
		return get_deflist(ctx, link, def->list_info,
				   key, value, (ARRAY_TYPE(void_array) *)ptr);
	case SET_STRLIST: {
		ctx->prev_info = &strlist_info;
		return get_deflist(ctx, link, &strlist_info,
				   key, value, (ARRAY_TYPE(void_array) *)ptr);
	}
	}

	i_unreached();
	return -1;
}

static int settings_parse_keyvalue(struct setting_parser_context *ctx,
				   const char *key, const char *value)
{
	const struct setting_define *def = NULL;
	unsigned int i;
	int ret = 1;

	/* try to find from roots */
	for (i = 0; i < ctx->root_count; i++) {
		def = setting_define_find(ctx->roots[i].info, key);
		if (def != NULL)
			break;
	}
	if (def != NULL) {
		if (settings_parse(ctx, &ctx->roots[i], def, key, value) < 0)
			ret = -1;
	} else {
		/* try to find from links */
		const char *end = strrchr(key, SETTINGS_SEPARATOR);
		struct setting_link *link;

		link = end == NULL ? NULL :
			hash_table_lookup(ctx->links, t_strdup_until(key, end));
		if (link == NULL)
			def = NULL;
		else if (link->info == &strlist_info) {
			void *vkey, *vvalue;

			vkey = p_strdup(ctx->set_pool, end + 1);
			vvalue = p_strdup(ctx->set_pool, value);
			array_append(link->array, &vkey, 1);
			array_append(link->array, &vvalue, 1);
			return 1;
		} else {
			def = setting_define_find(link->info, end + 1);
		}
		if (def != NULL) {
			if (settings_parse(ctx, link, def, key, value) < 0)
				ret = -1;
		} else {
			ctx->error = p_strconcat(ctx->parser_pool,
				"Unknown setting: ", key, NULL);
			ret = 0;
		}
	}
	return ret;
}

int settings_parse_line(struct setting_parser_context *ctx, const char *line)
{
	const char *key, *value;
	int ret;

	ctx->error = NULL;
	ctx->prev_info = NULL;

	key = line;
	value = strchr(line, '=');
	if (value == NULL) {
		ctx->error = "Missing '='";
		return -1;
	}

	if (key == value) {
		ctx->error = "Missing key name ('=' at the beginning of line)";
		return -1;
	}

	T_BEGIN {
		key = t_strdup_until(key, value);
		ret = settings_parse_keyvalue(ctx, key, value + 1);
	} T_END;
	return ret;
}

const struct setting_parser_info *
settings_parse_get_prev_info(struct setting_parser_context *ctx)
{
	return ctx->prev_info;
}

int settings_parse_stream(struct setting_parser_context *ctx,
			  struct istream *input)
{
	const char *line;
	string_t *full_line;
	size_t len;
	int ret = 1;

	full_line = str_new(default_pool, 512);
	while ((line = i_stream_next_line(input)) != NULL) {
		if (*line == '\0') {
			/* empty line finishes it */
			ret = 0;
			break;
		}

		ctx->linenum++;
		while (IS_WHITE(*line == ' '))
			line++;
		if (*line == '\0' || *line == '#')
			continue;

		len = strlen(line);
		while (len > 0 && IS_WHITE(line[len-1]))
			len--;
		if (line[len] == '\\') {
			/* line continues */
			str_append_n(full_line, line, len - 1);
		} else {
			/* full line */
			if (str_len(full_line) > 0) {
				str_append_n(full_line, line, len);
				line = str_c(full_line);
			}

			ret = settings_parse_line(ctx, line);
			if (ret == 0 &&
			    (ctx->flags &
			     SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS) == 0)
				ret = -1;

			if (ret < 0) {
				ctx->error = p_strdup_printf(ctx->parser_pool,
							     "Line %u: %s",
							     ctx->linenum,
							     ctx->error);
				ret = -1;
				break;
			}
			str_truncate(full_line, 0);
		}
	}
	str_free(&full_line);
	return ret;
}

int settings_parse_stream_read(struct setting_parser_context *ctx,
			       struct istream *input)
{
	const unsigned char *data;
	size_t size;
	int ret;

	while ((ret = i_stream_read(input)) > 0) {
		if (ctx->save_input_str != NULL) {
			data = i_stream_get_data(input, &size);
			str_append_n(ctx->save_input_str, data, size);
		}
		if ((ret = settings_parse_stream(ctx, input)) < 0)
			return -1;
		if (ret == 0) {
			/* empty line read */
			return 0;
		}
	}

	switch (ret) {
	case -1:
		if (input->stream_errno != 0) {
			ctx->error = p_strdup_printf(ctx->parser_pool,
						     "read() failed: %m");
		} else {
			ctx->error = "input is missing end-of-settings line";
		}
		break;
	case -2:
		ctx->error = p_strdup_printf(ctx->parser_pool,
					     "Line %u: line too long",
					     ctx->linenum);
		break;
	case 0:
		/* blocks */
		return 1;
	default:
		i_unreached();
	}
	return -1;
}

int settings_parse_file(struct setting_parser_context *ctx,
			const char *path, size_t max_line_length)
{
	struct istream *input;
	int fd, ret;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		ctx->error = p_strdup_printf(ctx->parser_pool,
					     "open(%s) failed: %m", path);
		return -1;
	}

	input = i_stream_create_fd(fd, max_line_length, TRUE);
	ret = settings_parse_stream_read(ctx, input);
	i_stream_unref(&input);

	return ret;
}

int settings_parse_environ(struct setting_parser_context *ctx)
{
	extern char **environ;
	const char *key, *value;
	unsigned int i;
	int ret = 0;

	for (i = 0; environ[i] != NULL && ret == 0; i++) {
		value = strchr(environ[i], '=');
		if (value != NULL) T_BEGIN {
			key = t_strdup_until(environ[i], value++);
			key = t_str_lcase(key);
			if (settings_parse_keyvalue(ctx, key, value) < 0) {
				ctx->error = p_strdup_printf(ctx->parser_pool,
					"Invalid setting %s: %s",
					key, ctx->error);
				ret = -1;
			}
		} T_END;
	}
	return ret;
}

int settings_parse_exec(struct setting_parser_context *ctx,
			const char *bin_path, const char *config_path,
			const char *service)
{
	struct istream *input;
	pid_t pid;
	int ret, fd[2], status;

	if (pipe(fd) < 0) {
		i_error("pipe() failed: %m");
		return -1;
	}

	pid = fork();
	if (pid == (pid_t)-1) {
		i_error("fork() failed: %m");
		(void)close(fd[0]);
		(void)close(fd[1]);
		return -1;
	}
	if (pid == 0) {
		/* child */
		static const char *argv[] = {
			NULL,
			"-c", NULL,
			"-s", NULL,
			NULL
		};
		argv[0] = bin_path;
		argv[2] = config_path;
		argv[4] = service;
		(void)close(fd[0]);
		if (dup2(fd[1], STDOUT_FILENO) < 0)
			i_fatal("dup2() failed: %m");

		execv(argv[0], (void *)argv);
		i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", bin_path);
		return -1;
	}
	(void)close(fd[1]);

	input = i_stream_create_fd(fd[0], (size_t)-1, TRUE);
	ret = settings_parse_stream_read(ctx, input);
	i_stream_destroy(&input);

	if (waitpid(pid, &status, 0) < 0) {
		i_error("waitpid() failed: %m");
		ret = -1;
	} else if (status != 0) {
		i_error("%s returned failure: %d", bin_path, status);
		ret = -1;
	}
	return ret;
}

void settings_parse_set_expanded(struct setting_parser_context *ctx,
				 bool is_expanded)
{
	ctx->str_vars_are_expanded = is_expanded;
}

static void
settings_var_expand_info(const struct setting_parser_info *info,
			 pool_t pool, void *set,
			 const struct var_expand_table *table, string_t *str)
{
	const struct setting_define *def;
	void *value, *const *children;
	unsigned int i, count;

	for (def = info->defines; def->key != NULL; def++) {
		value = PTR_OFFSET(set, def->offset);
		switch (def->type) {
		case SET_STR_VARS: {
			const char **val = value;

			if (*val == NULL)
				break;

			if (**val == SETTING_STRVAR_UNEXPANDED[0]) {
				str_truncate(str, 0);
				var_expand(str, *val + 1, table);
				*val = p_strdup(pool, str_c(str));
			} else {
				i_assert(**val == SETTING_STRVAR_EXPANDED[0]);
				*val += 1;
			}
			break;
		}
		case SET_DEFLIST: {
			const ARRAY_TYPE(void_array) *val = value;

			if (!array_is_created(val))
				break;

			children = array_get(val, &count);
			for (i = 0; i < count; i++) {
				settings_var_expand_info(def->list_info,
							 pool, children[i],
							 table, str);
			}
			break;
		}
		default:
			break;
		}
	}
}

void settings_var_expand(const struct setting_parser_info *info,
			 void *set, pool_t pool,
			 const struct var_expand_table *table)
{
	string_t *str;

	T_BEGIN {
		str = t_str_new(256);
		settings_var_expand_info(info, pool, set, table, str);
	} T_END;
}

bool settings_vars_have_key(const struct setting_parser_info *info, void *set,
			    char var_key, const char *long_var_key,
			    const char **key_r, const char **value_r)
{
	const struct setting_define *def;
	const void *value;
	void *const *children;
	unsigned int i, count;

	for (def = info->defines; def->key != NULL; def++) {
		value = CONST_PTR_OFFSET(set, def->offset);
		switch (def->type) {
		case SET_STR_VARS: {
			const char *const *val = value;

			if (*val == NULL)
				break;

			if (**val == SETTING_STRVAR_UNEXPANDED[0]) {
				if (var_has_key(*val + 1, var_key,
						long_var_key)) {
					*key_r = def->key;
					*value_r = *val + 1;
					return TRUE;
				}
			} else {
				i_assert(**val == SETTING_STRVAR_EXPANDED[0]);
			}
			break;
		}
		case SET_DEFLIST: {
			const ARRAY_TYPE(void_array) *val = value;

			if (!array_is_created(val))
				break;

			children = array_get(val, &count);
			for (i = 0; i < count; i++) {
				if (settings_vars_have_key(def->list_info,
							   children[i], var_key,
							   long_var_key,
							   key_r, value_r))
					return TRUE;
			}
			break;
		}
		default:
			break;
		}
	}
	return FALSE;
}

void *settings_dup(const struct setting_parser_info *info,
		   const void *set, pool_t pool)
{
	const struct setting_define *def;
	const void *src;
	void *dest_set, *dest, *const *children;
	unsigned int i, count;

	dest_set = p_malloc(pool, info->struct_size);
	memcpy(dest_set, set, info->struct_size);
	for (def = info->defines; def->key != NULL; def++) {
		src = CONST_PTR_OFFSET(set, def->offset);
		dest = PTR_OFFSET(dest_set, def->offset);

		switch (def->type) {
		case SET_INTERNAL:
		case SET_BOOL:
		case SET_UINT:
			break;
		case SET_STR_VARS:
		case SET_STR:
		case SET_ENUM: {
			const char *const *src_str = src;
			const char **dest_str = dest;

			*dest_str = p_strdup(pool, *src_str);
			break;
		}
		case SET_DEFLIST: {
			const ARRAY_TYPE(void_array) *src_arr = src;
			ARRAY_TYPE(void_array) *dest_arr = dest;
			void *child_set;

			if (!array_is_created(src_arr))
				break;

			children = array_get(src_arr, &count);
			p_array_init(dest_arr, pool, count);
			for (i = 0; i < count; i++) {
				child_set = settings_dup(def->list_info,
							 children[i], pool);
				array_append(dest_arr, &child_set, 1);
			}
			break;
		}
		case SET_STRLIST: {
			const ARRAY_TYPE(const_string) *src_arr = src;
			ARRAY_TYPE(const_string) *dest_arr = dest;
			const char *const *strings, *dup;

			if (!array_is_created(src_arr))
				break;

			strings = array_get(src_arr, &count);
			p_array_init(dest_arr, pool, count);
			for (i = 0; i < count; i += 2)
				dup = p_strdup(pool, strings[i]);
			break;
		}
		}
	}
	return dest_set;
}

void settings_parse_save_input(struct setting_parser_context *ctx,
			       string_t *dest)
{
	ctx->save_input_str = dest;
}

static void
info_update_real(pool_t pool, struct setting_parser_info *parent,
		 const struct dynamic_settings_parser *parsers)
{
	/* @UNSAFE */
	ARRAY_DEFINE(defines, struct setting_define);
	ARRAY_TYPE(dynamic_settings_parser) dynamic_parsers;
	struct dynamic_settings_parser new_parser;
	const struct setting_define *cur_defines;
	struct setting_define *new_defines, new_define;
	void *parent_defaults;
	unsigned int i, j;
	size_t offset, new_struct_size;

	t_array_init(&defines, 128);
	/* add existing defines */
	for (j = 0; parent->defines[j].key != NULL; j++)
		array_append(&defines, &parent->defines[j], 1);
	new_struct_size = parent->struct_size;

	/* add new dynamic defines */
	for (i = 0; parsers[i].name != NULL; i++) {
		i_assert(parsers[i].info->parent == parent);
		cur_defines = parsers[i].info->defines;
		for (j = 0; cur_defines[j].key != NULL; j++) {
			new_define = cur_defines[j];
			new_define.offset += new_struct_size;
			array_append(&defines, &new_define, 1);
		}
		new_struct_size += MEM_ALIGN(parsers[i].info->struct_size);
	}
	new_defines = p_new(pool, struct setting_define,
			    array_count(&defines) + 1);
	memcpy(new_defines, array_idx(&defines, 0),
	       sizeof(*parent->defines) * array_count(&defines));
	parent->defines = new_defines;

	/* update defaults */
	parent_defaults = p_malloc(pool, new_struct_size);
	memcpy(parent_defaults, parent->defaults, parent->struct_size);
	offset = parent->struct_size;
	for (i = 0; parsers[i].name != NULL; i++) {
		memcpy(PTR_OFFSET(parent_defaults, offset),
		       parsers[i].info->defaults, parsers[i].info->struct_size);
		offset += MEM_ALIGN(parsers[i].info->struct_size);
	}
	parent->defaults = parent_defaults;

	/* update dynamic parsers list */
	t_array_init(&dynamic_parsers, 32);
	if (parent->dynamic_parsers != NULL) {
		for (i = 0; parent->dynamic_parsers[i].name != NULL; i++) {
			array_append(&dynamic_parsers,
				     &parent->dynamic_parsers[i], 1);
		}
	}
	offset = parent->struct_size;
	for (i = 0; parsers[i].name != NULL; i++) {
		new_parser = parsers[i];
		new_parser.name = p_strdup(pool, new_parser.name);
		new_parser.struct_offset = offset;
		array_append(&dynamic_parsers, &new_parser, 1);
		offset += MEM_ALIGN(parsers[i].info->struct_size);
	}
	parent->dynamic_parsers =
		p_new(pool, struct dynamic_settings_parser,
		      array_count(&dynamic_parsers) + 1);
	memcpy(parent->dynamic_parsers, array_idx(&dynamic_parsers, 0),
	       sizeof(*parent->dynamic_parsers) *
	       array_count(&dynamic_parsers));
	parent->struct_size = new_struct_size;
}

void
settings_parser_info_update(pool_t pool, struct setting_parser_info *parent,
			    const struct dynamic_settings_parser *parsers)
{
	T_BEGIN {
		info_update_real(pool, parent, parsers);
	} T_END;
}

const void *settings_find_dynamic(struct setting_parser_info *info,
				  const void *base_set, const char *name)
{
	unsigned int i;

	if (info->dynamic_parsers == NULL)
		return NULL;

	for (i = 0; info->dynamic_parsers[i].name != NULL; i++) {
		if (strcmp(info->dynamic_parsers[i].name, name) == 0) {
			return CONST_PTR_OFFSET(base_set,
				info->dynamic_parsers[i].struct_offset);
		}
	}
	return NULL;
}