cfg/cfg_ctx.c
a903fdab
 /*
  * $Id$
  *
  * Copyright (C) 2007 iptelorg GmbH
  *
86e99f5a
  * This file is part of SIP-router, a free SIP server.
a903fdab
  *
86e99f5a
  * SIP-router is free software; you can redistribute it and/or modify
a903fdab
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
86e99f5a
  * SIP-router is distributed in the hope that it will be useful,
a903fdab
  * 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, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * History
  * -------
  *  2007-12-03	Initial version (Miklos)
  */
 
 #include <string.h>
e5cba078
 #include <stdio.h>
 #include <stdlib.h>
a903fdab
 
e5cba078
 #include "../ut.h"
a903fdab
 #include "cfg_struct.h"
ca88d956
 #include "cfg_script.h"
a903fdab
 #include "cfg_ctx.h"
 
e5cba078
 
a903fdab
 /* linked list of all the registered cfg contexts */
 static cfg_ctx_t	*cfg_ctx_list = NULL;
 
 /* creates a new config context that is an interface to the
  * cfg variables with write permission
  */
30feb9b6
 int cfg_register_ctx(cfg_ctx_t **handle, cfg_on_declare on_declare_cb)
a903fdab
 {
 	cfg_ctx_t	*ctx;
 	cfg_group_t	*group;
 	str		gname;
 
 	/* allocate memory for the new context
 	Better to use shm mem, because 'changed' and 'lock'
 	must be in shm mem anyway */
 	ctx = (cfg_ctx_t *)shm_malloc(sizeof(cfg_ctx_t));
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_register_ctx(): not enough shm memory\n");
30feb9b6
 		return -1;
a903fdab
 	}
 	memset(ctx, 0, sizeof(cfg_ctx_t));
 	if (lock_init(&ctx->lock) == 0) {
 		LOG(L_ERR, "ERROR: cfg_register_ctx(): failed to init lock\n");
 		shm_free(ctx);
30feb9b6
 		return -1;
a903fdab
 	}
 
 	/* add the new ctx to the beginning of the list */
 	ctx->next = cfg_ctx_list;
 	cfg_ctx_list = ctx;
 
30feb9b6
 	/* let the driver know about the already registered groups
 	 * The handle of the context must be set before calling the
 	 * on_declare callbacks. */
 	*handle = ctx;
a903fdab
 	if (on_declare_cb) {
 		ctx->on_declare_cb = on_declare_cb;
 
 		for (	group = cfg_group;
 			group;
 			group = group->next
 		) {
140137c9
 			/* dynamic groups are not ready, the callback
 			will be called later when the group is fixed-up */
fd958ea0
 			if (group->dynamic != CFG_GROUP_STATIC) continue;
140137c9
 
a903fdab
 			gname.s = group->name;
 			gname.len = group->name_len;
 			on_declare_cb(&gname, group->mapping->def);
 		}
 	}
 
30feb9b6
 	return 0;
a903fdab
 }
 
 /* free the memory allocated for the contexts */
 void cfg_ctx_destroy(void)
 {
 	cfg_ctx_t	*ctx, *ctx2;
 
 	for (	ctx = cfg_ctx_list;
 		ctx;
 		ctx = ctx2
 	) {
 		ctx2 = ctx->next;
 		shm_free(ctx);
 	}
 	cfg_ctx_list = NULL;
 }
 
 /* notify the drivers about the new config definition */
140137c9
 void cfg_notify_drivers(char *group_name, int group_name_len, cfg_def_t *def)
a903fdab
 {
 	cfg_ctx_t	*ctx;
 	str		gname;
 
 	gname.s = group_name;
140137c9
 	gname.len = group_name_len;
a903fdab
 
 	for (	ctx = cfg_ctx_list;
 		ctx;
 		ctx = ctx->next
 	)
 		if (ctx->on_declare_cb)
 			ctx->on_declare_cb(&gname, def);
 }
 
e5cba078
 /* placeholder for a temporary string */
 static char	*temp_string = NULL;
 
 /* convert the value to the requested type */
bcf3fff9
 int convert_val(unsigned int val_type, void *val,
a903fdab
 			unsigned int var_type, void **new_val)
 {
 	static str	s;
e5cba078
 	char		*end;
 	int		i;
 	static char	buf[INT2STR_MAX_LEN];
a903fdab
 
e5cba078
 	/* we have to convert from val_type to var_type */
 	switch (CFG_INPUT_MASK(var_type)) {
 	case CFG_INPUT_INT:
 		if (val_type == CFG_VAR_INT) {
a903fdab
 			*new_val = val;
 			break;
 
e5cba078
 		} else if (val_type == CFG_VAR_STRING) {
c55d3c7f
 			if (!val || (((char *)val)[0] == '\0')) {
 				LOG(L_ERR, "ERROR: convert_val(): "
 					"cannot convert NULL string value to integer\n");
 				return -1;
 			}
e5cba078
 			*new_val = (void *)(long)strtol((char *)val, &end, 10);
 			if (*end != '\0') {
 				LOG(L_ERR, "ERROR: convert_val(): "
 					"cannot convert string to integer '%s'\n",
c55d3c7f
 					(char *)val);
e5cba078
 				return -1;
a903fdab
 			}
e5cba078
 			break;
 
 		} else if (val_type == CFG_VAR_STR) {
c55d3c7f
 			if (!((str *)val)->len || !((str *)val)->s) {
 				LOG(L_ERR, "ERROR: convert_val(): "
 					"cannot convert NULL str value to integer\n");
 				return -1;
 			}
e5cba078
 			if (str2sint((str *)val, &i)) {
 				LOG(L_ERR, "ERROR: convert_val(): "
 					"cannot convert string to integer '%.*s'\n",
 					((str *)val)->len, ((str *)val)->s);
 				return -1;
 			}
 			*new_val = (void *)(long)i;
 			break;
 		}
 		goto error;
 
 	case CFG_INPUT_STRING:
 		if (val_type == CFG_VAR_INT) {
 			buf[snprintf(buf, sizeof(buf)-1, "%ld", (long)val)] = '\0';
 			*new_val = buf;
 			break;
 
 		} else if (val_type == CFG_VAR_STRING) {
a903fdab
 			*new_val = val;
 			break;
e5cba078
 
 		} else if (val_type == CFG_VAR_STR) {
c55d3c7f
 			if (!((str *)val)->s) {
 				*new_val = NULL;
 				break;
 			}
e5cba078
 			/* the value may not be zero-terminated, thus,
 			a new variable has to be allocated with larger memory space */
 			if (temp_string) pkg_free(temp_string);
 			temp_string = (char *)pkg_malloc(sizeof(char) * (((str *)val)->len + 1));
 			if (!temp_string) {
 				LOG(L_ERR, "ERROR: convert_val(): not enough memory\n");
 				return -1;
 			}
 			memcpy(temp_string, ((str *)val)->s, ((str *)val)->len);
 			temp_string[((str *)val)->len] = '\0';
 			*new_val = (void *)temp_string;
 			break;
 
 		}
 		goto error;
 
 	case CFG_INPUT_STR:
 		if (val_type == CFG_VAR_INT) {
 			s.len = snprintf(buf, sizeof(buf)-1, "%ld", (long)val);
 			buf[s.len] = '\0';
 			s.s = buf;
 			*new_val = (void *)&s;
 			break;
 
 		} else if (val_type == CFG_VAR_STRING) {
 			s.s = (char *)val;
c55d3c7f
 			s.len = (s.s) ? strlen(s.s) : 0;
e5cba078
 			*new_val = (void *)&s;
 			break;
 
 		} else if (val_type == CFG_VAR_STR) {
 			*new_val = val;
 			break;			
 		}
 		goto error;
a903fdab
 	}
 
 	return 0;
 
 error:
 	LOG(L_ERR, "ERROR: convert_val(): got a value with type %u, but expected %u\n",
 			val_type, CFG_INPUT_MASK(var_type));
 	return -1;
 }
 
ca88d956
 void convert_val_cleanup(void)
 {
 	if (temp_string) {
 		pkg_free(temp_string);
 		temp_string = NULL;
 	}
 }
e5cba078
 
6a44d0bd
 /* returns the size of the variable */
 static int cfg_var_size(cfg_mapping_t *var)
 {
 	switch (CFG_VAR_TYPE(var)) {
 
 	case CFG_VAR_INT:
 		return sizeof(int);
 
 	case CFG_VAR_STRING:
 		return sizeof(char *);
 
 	case CFG_VAR_STR:
 		return sizeof(str);
 
 	case CFG_VAR_POINTER:
 		return sizeof(void *);
 
 	default:
 		LOG(L_CRIT, "BUG: cfg_var_size(): unknown type: %u\n",
 			CFG_VAR_TYPE(var));
 		return 0;
 	}
 }
 
 /* Update the varibales of the array within the meta structure
  * with the new default value.
  * The array is cloned before a change if clone is set to 1.
  */
 static int cfg_update_defaults(cfg_group_meta_t	*meta,
 				cfg_group_t *group, cfg_mapping_t *var, char *new_val,
 				int clone)
 {
 	int	i, clone_done=0;
 	cfg_group_inst_t *array, *ginst;
 
8e1d174c
 	if (!(array = meta->array))
 		return 0;
6a44d0bd
 	for (i = 0; i < meta->num; i++) {
 		ginst = (cfg_group_inst_t *)((char *)array
8e1d174c
 			+ (sizeof(cfg_group_inst_t) + group->size - 1) * i);
6a44d0bd
 
 		if (!CFG_VAR_TEST(ginst, var)) {
 			/* The variable uses the default value, it needs to be rewritten. */
 			if (clone && !clone_done) {
 				/* The array needs to be cloned before the modification */
 				if (!(array = cfg_clone_array(meta, group)))
 					return -1;
b4e2f2aa
 				ginst = (cfg_group_inst_t *)translate_pointer((char *)array,
 								(char *)meta->array,
 								(char *)ginst);
6a44d0bd
 				/* re-link the array to the meta-data */
 				meta->array = array;
 				clone_done = 1;
 			}
 			memcpy(ginst->vars + var->offset, new_val, cfg_var_size(var)); 
 		}
 	}
 	return 0;
 }
 
cabd961c
 /* sets the value of a variable without the need of commit
  *
  * return value:
  *   0: success
  *  -1: error
  *   1: variable has not been found
  */
6a44d0bd
 int cfg_set_now(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
a903fdab
 			void *val, unsigned int val_type)
 {
27580846
 	int		i;
a903fdab
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*p, *v;
 	cfg_block_t	*block = NULL;
33bfeb9d
 	str		s, s2;
a903fdab
 	char		*old_string = NULL;
27580846
 	void		**replaced = NULL;
a903fdab
 	cfg_child_cb_t	*child_cb = NULL;
678625a4
 	cfg_group_inst_t	*group_inst = NULL, *new_array = NULL;
6a44d0bd
 	unsigned char		*var_block;
a903fdab
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_set_now(): context is undefined\n");
 		return -1;
 	}
 
7d7264e2
 	if ((val_type == CFG_VAR_UNSET) && !group_id) {
 		LOG(L_ERR, "ERROR: cfg_set_now(): Only group instance values can be deleted\n");
 		return -1;
 	}
 
6a44d0bd
 	if (group_id && !cfg_shmized) {
 		/* The config group has not been shmized yet,
 		but an additional instance of a variable needs to be added to the group.
 		Add this instance to the linked list of variables, they
 		will be fixed later. */
eddc94d7
 		return new_add_var(group_name, *group_id, var_name,
 				val, val_type);
6a44d0bd
 	}
 
a903fdab
 	/* look-up the group and the variable */
ca88d956
 	if (cfg_lookup_var(group_name, var_name, &group, &var)) {
 		if (!cfg_shmized) {
 			/* The group may be dynamic which is not yet ready
 			 * before forking */
 			if ((group = cfg_lookup_group(group_name->s, group_name->len))
 				&& (group->dynamic == CFG_GROUP_DYNAMIC)
 			)
 				return cfg_set_script_var(group, var_name, val, val_type);
 		}
cabd961c
 		return 1;
ca88d956
 	}
b9c10056
 		
 	/* check whether the variable is read-only */
 	if (var->def->type & CFG_READONLY) {
 		LOG(L_ERR, "ERROR: cfg_set_now(): variable is read-only\n");
 		goto error0;
 	}
a903fdab
 
6a44d0bd
 	/* The additional variable instances having per-child process callback
 	 * with CFG_CB_ONLY_ONCE flag cannot be rewritten.
 	 * The reason is that such variables typically set global parameters
 	 * as opposed to per-process variables. Hence, it is not possible to set
 	 * the group handle temporary to another block, and then reset it back later. */
 	if (group_id
 		&& var->def->on_set_child_cb
 		&& var->def->type & CFG_CB_ONLY_ONCE
 	) {
 		LOG(L_ERR, "ERROR: cfg_set_now(): This variable does not support muliple values.\n");
 		goto error0;
 	}
 
a903fdab
 	/* check whether we have to convert the type */
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
 		&& convert_val(val_type, val, CFG_INPUT_TYPE(var), &v)
 	)
a903fdab
 		goto error0;
 	
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
 	&& (CFG_INPUT_TYPE(var) == CFG_INPUT_INT) 
29787f15
 	&& (var->def->min || var->def->max)) {
 		/* perform a simple min-max check for integers */
 		if (((int)(long)v < var->def->min)
 		|| ((int)(long)v > var->def->max)) {
 			LOG(L_ERR, "ERROR: cfg_set_now(): integer value is out of range\n");
 			goto error0;
 		}
 	}
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
 		&& var->def->on_change_cb
 	) {
a903fdab
 		/* Call the fixup function.
 		There is no need to set a temporary cfg handle,
 		becaue a single variable is changed */
6a44d0bd
 		if (!group_id) {
 			var_block = *(group->handle);
 		} else {
 			if (!cfg_local) {
 				LOG(L_ERR, "ERROR: cfg_set_now(): Local configuration is missing\n");
 				goto error0;
 			}
 			group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
 							group->size,
 							*group_id);
 			if (!group_inst) {
5d4d6ae9
 				LOG(L_ERR, "ERROR: cfg_set_now(): local group instance %.*s[%u] is not found\n",
 					group_name->len, group_name->s, *group_id);
6a44d0bd
 				goto error0;
 			}
 			var_block = group_inst->vars;
 		}
 
 		if (var->def->on_change_cb(var_block,
33bfeb9d
 						group_name,
a903fdab
 						var_name,
 						&v) < 0) {
 			LOG(L_ERR, "ERROR: cfg_set_now(): fixup failed\n");
 			goto error0;
 		}
 
 	}
 
6a44d0bd
 	/* Set the per-child process callback only if the default value is changed.
 	 * The callback of other instances will be called when the config is
 	 * switched to that instance. */
 	if (!group_id && var->def->on_set_child_cb) {
d8bad606
 		/* get the name of the variable from the internal struct,
 		because var_name may be freed before the callback needs it */
33bfeb9d
 		s.s = group->name;
 		s.len = group->name_len;
 		s2.s = var->def->name;
 		s2.len = var->name_len;
 		child_cb = cfg_child_cb_new(&s, &s2,
ceef8440
 					var->def->on_set_child_cb,
 					var->def->type);
a0b44e92
 		if (!child_cb) {
 			LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
 			goto error0;
a903fdab
 		}
a0b44e92
 	}
a903fdab
 
a0b44e92
 	if (cfg_shmized) {
a903fdab
 		/* make sure that nobody else replaces the global config
 		while the new one is prepared */
 		CFG_WRITER_LOCK();
 
6a44d0bd
 		if (group_id) {
 			group_inst = cfg_find_group(CFG_GROUP_META(*cfg_global, group),
 							group->size,
 							*group_id);
 			if (!group_inst) {
5d4d6ae9
 				LOG(L_ERR, "ERROR: cfg_set_now(): global group instance %.*s[%u] is not found\n",
 					group_name->len, group_name->s, *group_id);
a138e2b1
 				goto error;
6a44d0bd
 			}
7d7264e2
 			if ((val_type == CFG_VAR_UNSET) && !CFG_VAR_TEST(group_inst, var)) {
 				/* nothing to do, the variable is not set in the group instance */
 				CFG_WRITER_UNLOCK();
 				LOG(L_DBG, "DEBUG: cfg_set_now(): The variable is not set\n");
 				return 0;
 			}
6a44d0bd
 			var_block = group_inst->vars;
 		} else {
 			group_inst = NULL;
 			var_block = CFG_GROUP_DATA(*cfg_global, group);
 		}
 
0d96457b
 		if (var->def->type & CFG_ATOMIC) {
 			/* atomic change is allowed, we can rewrite the value
 			directly in the global config */
6a44d0bd
 			p = var_block + var->offset;
a903fdab
 
0d96457b
 		} else {
 			/* clone the memory block, and prepare the modification */
 			if (!(block = cfg_clone_global())) goto error;
 
6a44d0bd
 			if (group_inst) {
 				/* The additional array of the group needs to be also cloned.
 				 * When any of the variables within this array is changed, then
 				 * the complete config block and this array is replaced. */
 				if (!(new_array = cfg_clone_array(CFG_GROUP_META(*cfg_global, group), group)))
 					goto error;
b4e2f2aa
 				group_inst = (cfg_group_inst_t *)translate_pointer((char *)new_array,
 					(char *)CFG_GROUP_META(*cfg_global, group)->array,
 					(char *)group_inst);
6a44d0bd
 				var_block = group_inst->vars;
 				CFG_GROUP_META(block, group)->array = new_array;
 			} else {
 				/* The additional array may need to be replaced depending
 				 * on whether or not there is any variable in the array set
 				 * to the default value which is changed now. If this is the case,
 				 * then the array will be replaced later when the variables are
 				 * updated.
 				 */
 				var_block = CFG_GROUP_DATA(block, group);
 			}
 			p = var_block + var->offset;
0d96457b
 		}
a903fdab
 	} else {
140137c9
 		/* we are allowed to rewrite the value on-the-fly
 		The handle either points to group->vars, or to the
 		shared memory block (dynamic group) */
 		p = *(group->handle) + var->offset;
a903fdab
 	}
 
7d7264e2
 	if (val_type != CFG_VAR_UNSET) {
 		/* set the new value */
 		switch (CFG_VAR_TYPE(var)) {
 		case CFG_VAR_INT:
 			*(int *)p = (int)(long)v;
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_STRING:
 			/* clone the string to shm mem */
 			s.s = v;
 			s.len = (s.s) ? strlen(s.s) : 0;
 			if (cfg_clone_str(&s, &s)) goto error;
 			old_string = *(char **)p;
 			*(char **)p = s.s;
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_STR:
 			/* clone the string to shm mem */
 			s = *(str *)v;
 			if (cfg_clone_str(&s, &s)) goto error;
 			old_string = *(char **)p;
 			memcpy(p, &s, sizeof(str));
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_POINTER:
 			*(void **)p = v;
 			break;
a903fdab
 
7d7264e2
 		}
 		if (group_inst && !CFG_VAR_TEST_AND_SET(group_inst, var))
 			old_string = NULL; /* the string is the same as the default one,
 						it cannot be freed */
 	} else {
 		/* copy the default value to the group instance */
 		if ((CFG_VAR_TYPE(var) == CFG_VAR_STRING)
 		|| (CFG_VAR_TYPE(var) == CFG_VAR_STR))
 			old_string = *(char **)p;
 		memcpy(p, CFG_GROUP_DATA(*cfg_global, group) + var->offset, cfg_var_size(var)); 
 
 		CFG_VAR_TEST_AND_RESET(group_inst, var);
a903fdab
 	}
 
 	if (cfg_shmized) {
8e1d174c
 		if (!group_inst) {
 			/* the default value is changed, the copies of this value
 			need to be also updated */
 			if (cfg_update_defaults(CFG_GROUP_META(block ? block : *cfg_global, group),
6a44d0bd
 						group, var, p,
8e1d174c
 						block ? 1 : 0) /* clone if needed */
 			)
6a44d0bd
 				goto error;
8e1d174c
 			if (block && (CFG_GROUP_META(block, group)->array != CFG_GROUP_META(*cfg_global, group)->array))
6a44d0bd
 				new_array = CFG_GROUP_META(block, group)->array;
 		}
 
27580846
 		if (old_string || new_array) {
a903fdab
 			/* prepare the array of the replaced strings,
27580846
 			and replaced group instances,
a903fdab
 			they will be freed when the old block is freed */
27580846
 			replaced = (void **)shm_malloc(sizeof(void *)
 					* ((old_string?1:0) + (new_array?1:0) + 1));
a903fdab
 			if (!replaced) {
 				LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
 				goto error;
 			}
27580846
 			i = 0;
 			if (old_string) {
 				replaced[i] = old_string;
 				i++;
 			}
 			if (new_array) {	
 				replaced[i] = CFG_GROUP_META(*cfg_global, group)->array;
 				i++;
 			}
 			replaced[i] = NULL;
a903fdab
 		}
 		/* replace the global config with the new one */
0d96457b
 		if (block) cfg_install_global(block, replaced, child_cb, child_cb);
a903fdab
 		CFG_WRITER_UNLOCK();
 	} else {
140137c9
 		/* cfg_set() may be called more than once before forking */
 		if (old_string && (var->flag & cfg_var_shmized))
 			shm_free(old_string);
 
a903fdab
 		/* flag the variable because there is no need
 		to shmize it again */
 		var->flag |= cfg_var_shmized;
a0b44e92
 
 		/* the global config does not have to be replaced,
 		but the child callback has to be installed, otherwise the
 		child processes will miss the change */
0e41da75
 		if (child_cb)
 			cfg_install_child_cb(child_cb, child_cb);
a903fdab
 	}
 
 	if (val_type == CFG_VAR_INT)
 		LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
 			"has been changed to %d\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			(int)(long)val);
e5cba078
 
 	else if (val_type == CFG_VAR_STRING)
a903fdab
 		LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
 			"has been changed to \"%s\"\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			(char *)val);
 
7d7264e2
 	else if (val_type == CFG_VAR_STR)
e5cba078
 		LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
 			"has been changed to \"%.*s\"\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->s);
7d7264e2
 
 	else if (val_type == CFG_VAR_UNSET)
 		LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
 			"has been deleted\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s);
 
 	else
 		LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
 			"has been changed\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s);
 
1e3ac66d
 	if (group_id)
 		LOG(L_INFO, "INFO: cfg_set_now(): group id = %u\n",
 			*group_id);
e5cba078
 
 	convert_val_cleanup();
a903fdab
 	return 0;
 
 error:
 	if (cfg_shmized) CFG_WRITER_UNLOCK();
 	if (block) cfg_block_free(block);
6a44d0bd
 	if (new_array) shm_free(new_array);
67172188
 	if (child_cb) cfg_child_cb_free_list(child_cb);
28d46c65
 	if (replaced) shm_free(replaced);
a903fdab
 
 error0:
 	LOG(L_ERR, "ERROR: cfg_set_now(): failed to set the variable: %.*s.%.*s\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s);
 
 
e5cba078
 	convert_val_cleanup();
a903fdab
 	return -1;
 }
 
 /* wrapper function for cfg_set_now */
6a44d0bd
 int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			int val)
a903fdab
 {
6a44d0bd
 	return cfg_set_now(ctx, group_name, group_id, var_name,
 				(void *)(long)val, CFG_VAR_INT);
a903fdab
 }
 
 /* wrapper function for cfg_set_now */
6a44d0bd
 int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			char *val)
a903fdab
 {
6a44d0bd
 	return cfg_set_now(ctx, group_name, group_id, var_name,
 				(void *)val, CFG_VAR_STRING);
a903fdab
 }
 
e5cba078
 /* wrapper function for cfg_set_now */
6a44d0bd
 int cfg_set_now_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			str *val)
a903fdab
 {
6a44d0bd
 	return cfg_set_now(ctx, group_name, group_id, var_name,
 				(void *)val, CFG_VAR_STR);
a903fdab
 }
 
7d7264e2
 /* Delete a variable from the group instance.
  * wrapper function for cfg_set_now */
 int cfg_del_now(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name)
 {
 	return cfg_set_now(ctx, group_name, group_id, var_name,
 				NULL, CFG_VAR_UNSET);
 }
 
cabd961c
 /* sets the value of a variable but does not commit the change
  *
  * return value:
  *   0: success
  *  -1: error
  *   1: variable has not been found
  */
6a44d0bd
 int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
a903fdab
 			void *val, unsigned int val_type)
 {
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*v;
ec5bc6bb
 	unsigned char	*temp_handle;
a903fdab
 	int		temp_handle_created;
ec5bc6bb
 	cfg_changed_var_t	*changed = NULL, **changed_p;
1b366aa1
 	int		size;
a903fdab
 	str		s;
678625a4
 	cfg_group_inst_t	*group_inst = NULL;
ec5bc6bb
 	unsigned char		*var_block;
a903fdab
 
 	if (!cfg_shmized)
 		/* the cfg has not been shmized yet, there is no
 		point in registering the change and committing it later */
6a44d0bd
 		return cfg_set_now(ctx, group_name, group_id, var_name,
a903fdab
 					val, val_type);
 
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_set_delayed(): context is undefined\n");
 		return -1;
 	}
 
7d7264e2
 	if ((val_type == CFG_VAR_UNSET) && !group_id) {
 		LOG(L_ERR, "ERROR: cfg_set_delayed(): Only group instance values can be deleted\n");
 		return -1;
 	}
 
a903fdab
 	/* look-up the group and the variable */
 	if (cfg_lookup_var(group_name, var_name, &group, &var))
cabd961c
 		return 1;
a903fdab
 
b9c10056
 	/* check whether the variable is read-only */
 	if (var->def->type & CFG_READONLY) {
 		LOG(L_ERR, "ERROR: cfg_set_delayed(): variable is read-only\n");
 		goto error0;
 	}
 
ec5bc6bb
 	/* The additional variable instances having per-child process callback
 	 * with CFG_CB_ONLY_ONCE flag cannot be rewritten.
 	 * The reason is that such variables typically set global parameters
 	 * as opposed to per-process variables. Hence, it is not possible to set
 	 * the group handle temporary to another block, and then reset it back later. */
 	if (group_id
 		&& var->def->on_set_child_cb
 		&& var->def->type & CFG_CB_ONLY_ONCE
 	) {
eddc94d7
 		LOG(L_ERR, "ERROR: cfg_set_delayed(): This variable does not support muliple values.\n");
ec5bc6bb
 		goto error0;
 	}
 
a903fdab
 	/* check whether we have to convert the type */
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
 		&& convert_val(val_type, val, CFG_INPUT_TYPE(var), &v)
 	)
a903fdab
 		goto error0;
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
 	&& (CFG_INPUT_TYPE(var) == CFG_INPUT_INT) 
29787f15
 	&& (var->def->min || var->def->max)) {
 		/* perform a simple min-max check for integers */
 		if (((int)(long)v < var->def->min)
 		|| ((int)(long)v > var->def->max)) {
 			LOG(L_ERR, "ERROR: cfg_set_delayed(): integer value is out of range\n");
1813606a
 			goto error0;
29787f15
 		}
 	}
 
a903fdab
 	/* the ctx must be locked while reading and writing
 	the list of changed variables */
 	CFG_CTX_LOCK(ctx);
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET) && var->def->on_change_cb) {
a903fdab
 		/* The fixup function must see also the
 		not yet committed values, so a temporary handle
 		must be prepared that points to the new config.
 		Only the values within the group are applied,
 		other modifications are not visible to the callback.
 		The local config is the base. */
ec5bc6bb
 		if (!group_id) {
 			var_block = *(group->handle);
 		} else {
 			if (!cfg_local) {
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): Local configuration is missing\n");
 				goto error;
 			}
 			group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
 							group->size,
 							*group_id);
 			if (!group_inst) {
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): local group instance %.*s[%u] is not found\n",
 					group_name->len, group_name->s, *group_id);
 				goto error;
 			}
 			var_block = group_inst->vars;
 		}
a903fdab
 
 		if (ctx->changed_first) {
ec5bc6bb
 			temp_handle = (unsigned char *)pkg_malloc(group->size);
a903fdab
 			if (!temp_handle) {
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): "
 					"not enough memory\n");
 				goto error;
 			}
 			temp_handle_created = 1;
ec5bc6bb
 			memcpy(temp_handle, var_block, group->size);
a903fdab
 
 			/* apply the changes */
 			for (	changed = ctx->changed_first;
 				changed;
 				changed = changed->next
 			) {
ec5bc6bb
 				if (changed->group != group)
 					continue;
 				if ((!group_id && !changed->group_id_set) /* default values */
 					|| (group_id && !changed->group_id_set
 						&& !CFG_VAR_TEST(group_inst, changed->var))
 							/* default value is changed which affects the group_instance */
 					|| (group_id && changed->group_id_set
 						&& (*group_id == changed->group_id))
 							/* change within the group instance */
 				)
 					memcpy(	temp_handle + changed->var->offset,
 						changed->new_val.vraw,
 						cfg_var_size(changed->var));
a903fdab
 			}
 		} else {
 			/* there is not any change */
ec5bc6bb
 			temp_handle = var_block;
a903fdab
 			temp_handle_created = 0;
 		}
 			
 		if (var->def->on_change_cb(temp_handle,
33bfeb9d
 						group_name,
a903fdab
 						var_name,
 						&v) < 0) {
 			LOG(L_ERR, "ERROR: cfg_set_delayed(): fixup failed\n");
 			if (temp_handle_created) pkg_free(temp_handle);
 			goto error;
 		}
 		if (temp_handle_created) pkg_free(temp_handle);
 
 	}
 
 	/* everything went ok, we can add the new value to the list */
7d7264e2
 	size = sizeof(cfg_changed_var_t)
 		- sizeof(((cfg_changed_var_t*)0)->new_val)
 		+ ((val_type != CFG_VAR_UNSET) ? cfg_var_size(var) : 0);
a903fdab
 	changed = (cfg_changed_var_t *)shm_malloc(size);
 	if (!changed) {
 		LOG(L_ERR, "ERROR: cfg_set_delayed(): not enough shm memory\n");
 		goto error;
 	}
 	memset(changed, 0, size);
 	changed->group = group;
 	changed->var = var;
ec5bc6bb
 	if (group_id) {
 		changed->group_id = *group_id;
 		changed->group_id_set = 1;
 	}
a903fdab
 
7d7264e2
 	if (val_type != CFG_VAR_UNSET) {
 		switch (CFG_VAR_TYPE(var)) {
a903fdab
 
7d7264e2
 		case CFG_VAR_INT:
 			changed->new_val.vint = (int)(long)v;
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_STRING:
 			/* clone the string to shm mem */
 			s.s = v;
 			s.len = (s.s) ? strlen(s.s) : 0;
 			if (cfg_clone_str(&s, &s)) goto error;
 			changed->new_val.vp = s.s;
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_STR:
 			/* clone the string to shm mem */
 			s = *(str *)v;
 			if (cfg_clone_str(&s, &s)) goto error;
 			changed->new_val.vstr=s;
 			break;
a903fdab
 
7d7264e2
 		case CFG_VAR_POINTER:
 			changed->new_val.vp=v;
 			break;
a903fdab
 
7d7264e2
 		}
 	} else {
 		changed->del_value = 1;
a903fdab
 	}
 
ec5bc6bb
 	/* Order the changes by group + group_id + original order.
 	 * Hence, the list is still kept in order within the group.
 	 * The changes can be committed faster this way, the group instances
 	 * do not have to be looked-up for each and every variable. */
 	/* Check whether there is any variable in the list which
 	belongs to the same group */
 	for (	changed_p = &ctx->changed_first;
 		*changed_p && ((*changed_p)->group != changed->group);
 		changed_p = &(*changed_p)->next);
 	/* try to find the group instance, and move changed_p to the end of
 	the instance. */
 	for (	;
 		*changed_p
 			&& ((*changed_p)->group == changed->group)
 			&& (!(*changed_p)->group_id_set
 				|| ((*changed_p)->group_id_set && changed->group_id_set
 					&& ((*changed_p)->group_id <= changed->group_id)));
 		changed_p = &(*changed_p)->next);
 	/* Add the new variable before *changed_p */
 	changed->next = *changed_p;
 	*changed_p = changed;
a903fdab
 
 	CFG_CTX_UNLOCK(ctx);
 
 	if (val_type == CFG_VAR_INT)
 		LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
 			"is going to be changed to %d "
 			"[context=%p]\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			(int)(long)val,
 			ctx);
e5cba078
 
 	else if (val_type == CFG_VAR_STRING)
a903fdab
 		LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
 			"is going to be changed to \"%s\" "
 			"[context=%p]\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			(char *)val,
 			ctx);
 
7d7264e2
 	else if (val_type == CFG_VAR_STR)
e5cba078
 		LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
 			"is going to be changed to \"%.*s\" "
 			"[context=%p]\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->s,
 			ctx);
7d7264e2
 
 	else if (val_type == CFG_VAR_UNSET)
 		LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
 			"is going to be deleted "
 			"[context=%p]\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			ctx);
 
 	else
 		LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
 			"is going to be changed "
 			"[context=%p]\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			ctx);
 
ec5bc6bb
 	if (group_id)
 		LOG(L_INFO, "INFO: cfg_set_delayed(): group id = %u "
 			"[context=%p]\n",
 			*group_id,
 			ctx);
e5cba078
 
 	convert_val_cleanup();
a903fdab
 	return 0;
 
 error:
 	CFG_CTX_UNLOCK(ctx);
 	if (changed) shm_free(changed);
 error0:
 	LOG(L_ERR, "ERROR: cfg_set_delayed(): failed to set the variable: %.*s.%.*s\n",
 			group_name->len, group_name->s,
 			var_name->len, var_name->s);
 
e5cba078
 	convert_val_cleanup();
a903fdab
 	return -1;
 }
 
 /* wrapper function for cfg_set_delayed */
6a44d0bd
 int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 				int val)
a903fdab
 {
6a44d0bd
 	return cfg_set_delayed(ctx, group_name, group_id, var_name,
 				(void *)(long)val, CFG_VAR_INT);
a903fdab
 }
 
 /* wrapper function for cfg_set_delayed */
6a44d0bd
 int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 				char *val)
a903fdab
 {
6a44d0bd
 	return cfg_set_delayed(ctx, group_name, group_id, var_name,
 				(void *)val, CFG_VAR_STRING);
a903fdab
 }
 
e5cba078
 /* wrapper function for cfg_set_delayed */
6a44d0bd
 int cfg_set_delayed_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 				str *val)
e5cba078
 {
6a44d0bd
 	return cfg_set_delayed(ctx, group_name, group_id, var_name,
 				(void *)val, CFG_VAR_STR);
e5cba078
 }
 
7d7264e2
 /* Delete a variable from the group instance.
  * wrapper function for cfg_set_delayed */
 int cfg_del_delayed(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name)
 {
 	return cfg_set_delayed(ctx, group_name, group_id, var_name,
 				NULL, CFG_VAR_UNSET);
 }
 
a903fdab
 /* commits the previously prepared changes within the context */
 int cfg_commit(cfg_ctx_t *ctx)
 {
 	int	replaced_num = 0;
 	cfg_changed_var_t	*changed, *changed2;
ec5bc6bb
 	cfg_block_t	*block = NULL;
27580846
 	void	**replaced = NULL;
a903fdab
 	cfg_child_cb_t	*child_cb;
 	cfg_child_cb_t	*child_cb_first = NULL;
 	cfg_child_cb_t	*child_cb_last = NULL;
 	int	size;
 	void	*p;
33bfeb9d
 	str	s, s2;
ec5bc6bb
 	cfg_group_t	*group;
678625a4
 	cfg_group_inst_t	*group_inst = NULL;
a903fdab
 
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_commit(): context is undefined\n");
 		return -1;
 	}
 
 	if (!cfg_shmized) return 0; /* nothing to do */
 
 	/* the ctx must be locked while reading and writing
 	the list of changed variables */
 	CFG_CTX_LOCK(ctx);
 
 	/* is there any change? */
 	if (!ctx->changed_first) goto done;
 
ec5bc6bb
 	/* Count the number of replaced strings,
 	and replaced group arrays.
 	Prepare the linked list of per-child process
 	callbacks, that will be added to the global list. */
 	for (	changed = ctx->changed_first, group = NULL;
a903fdab
 		changed;
 		changed = changed->next
 	) {
ec5bc6bb
 		/* Each string/str potentially causes an old string to be freed
 		 * unless the variable of an additional group instance is set
 		 * which uses the default value. This case cannot be determined
 		 * without locking *cfg_global, hence, it is better to count these
 		 * strings as well even though the slot might not be used later. */
a903fdab
 		if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 		|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 			replaced_num++;
 
ec5bc6bb
 		/* See the above comments for strings */
 		if (group != changed->group) {
 			replaced_num++;
 			group = changed->group;
 		}
a903fdab
 
ec5bc6bb
 		if (!changed->group_id_set && changed->var->def->on_set_child_cb) {
33bfeb9d
 			s.s = changed->group->name;
 			s.len = changed->group->name_len;
 			s2.s = changed->var->def->name;
 			s2.len = changed->var->name_len;
 			child_cb = cfg_child_cb_new(&s, &s2,
ceef8440
 					changed->var->def->on_set_child_cb,
 					changed->var->def->type);
a903fdab
 			if (!child_cb) goto error0;
 
 			if (child_cb_last)
 				child_cb_last->next = child_cb;
 			else
 				child_cb_first = child_cb;
 			child_cb_last = child_cb;
 		}
 	}
 
c55d3c7f
 	if (replaced_num) {
 		/* allocate memory for the replaced string array */
27580846
 		size = sizeof(void *)*(replaced_num + 1);
 		replaced = (void **)shm_malloc(size);
c55d3c7f
 		if (!replaced) {
 			LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
ec5bc6bb
 			goto error0;
c55d3c7f
 		}
 		memset(replaced, 0 , size);
a903fdab
 	}
 
 	/* make sure that nobody else replaces the global config
 	while the new one is prepared */
 	CFG_WRITER_LOCK();
 
 	/* clone the memory block, and prepare the modification */
ec5bc6bb
 	if (!(block = cfg_clone_global()))
a903fdab
 		goto error;
 
ec5bc6bb
 	/* Apply the modifications to the buffer.
 	Note that the cycle relies on the order of the groups and group instances, i.e.
 	the order is group + group_id + order of commits. */
a903fdab
 	replaced_num = 0;
ec5bc6bb
 	for (	changed = ctx->changed_first, group = NULL; /* group points to the
 							last group array that has been cloned */
a903fdab
 		changed;
 		changed = changed->next
 	) {
ec5bc6bb
 		if (!changed->group_id_set) {
 			p = CFG_GROUP_DATA(block, changed->group)
 				+ changed->var->offset;
678625a4
 			group_inst = NULL; /* force the look-up of the next group_inst */
ec5bc6bb
 		} else {
 			if (group != changed->group) {
 				/* The group array has not been cloned yet. */
 				group = changed->group;
 				if (!(CFG_GROUP_META(block, group)->array = 
 					cfg_clone_array(CFG_GROUP_META(*cfg_global, group), group))
99b680c3
 				) {
59d6804b
 					LOG(L_ERR, "ERROR: cfg_commit(): group array cannot be cloned for %.*s[%u]\n",
99b680c3
 						group->name_len, group->name, changed->group_id);
ec5bc6bb
 					goto error;
99b680c3
 				}
a903fdab
 
ec5bc6bb
 				replaced[replaced_num] = CFG_GROUP_META(*cfg_global, group)->array;
 				replaced_num++;
 
 				group_inst = NULL; /* fore the look-up of group_inst */
 			}
 			if (!group_inst || (group_inst->id != changed->group_id)) {
 				group_inst = cfg_find_group(CFG_GROUP_META(block, group),
 								group->size,
 								changed->group_id);
 			}
 			if (!group_inst) {
59d6804b
 				LOG(L_ERR, "ERROR: cfg_commit(): global group instance %.*s[%u] is not found\n",
ec5bc6bb
 					group->name_len, group->name, changed->group_id);
 				goto error;
 			}
 			p = group_inst->vars + changed->var->offset;
 		}
 
7d7264e2
 		if (((changed->group_id_set && !changed->del_value && CFG_VAR_TEST_AND_SET(group_inst, changed->var))
 			|| (changed->group_id_set && changed->del_value && CFG_VAR_TEST_AND_RESET(group_inst, changed->var))
ec5bc6bb
 			|| !changed->group_id_set)
 		&& ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
7d7264e2
 			|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
ec5bc6bb
 		) {
1b366aa1
 			replaced[replaced_num] = *(char **)p;
c55d3c7f
 			if (replaced[replaced_num])
 				replaced_num++;
 			/* else do not increase replaced_num, because
 			the cfg_block_free() will stop at the first
 			NULL value */
a903fdab
 		}
 
7d7264e2
 		if (!changed->del_value)
 			memcpy(	p,
 				changed->new_val.vraw,
 				cfg_var_size(changed->var));
 		else
 			memcpy(	p,
 				CFG_GROUP_DATA(block, changed->group) + changed->var->offset,
 				cfg_var_size(changed->var));
 
ec5bc6bb
 
 		if (!changed->group_id_set) {
 			/* the default value is changed, the copies of this value
 			need to be also updated */
 			if (cfg_update_defaults(CFG_GROUP_META(block, changed->group),
 						changed->group, changed->var, p,
 						(group != changed->group)) /* clone if the array
 									has not been cloned yet */
                         )
                                 goto error;
                         if ((group != changed->group)
 				&& (CFG_GROUP_META(block, changed->group)->array != CFG_GROUP_META(*cfg_global, changed->group)->array)
 			) {
 				/* The array has been cloned */
 				group = changed->group;
 
 				replaced[replaced_num] = CFG_GROUP_META(*cfg_global, group)->array;
 				replaced_num++;
 			}
 		}
a903fdab
 	}
 
 	/* replace the global config with the new one */
 	cfg_install_global(block, replaced, child_cb_first, child_cb_last);
 	CFG_WRITER_UNLOCK();
 
ec5bc6bb
 	/* free the changed list */
a903fdab
 	for (	changed = ctx->changed_first;
 		changed;
 		changed = changed2
 	) {
 		changed2 = changed->next;
 		shm_free(changed);
 	}
 	ctx->changed_first = NULL;
 
 done:
 	LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
 			"[context=%p]\n",
 			ctx);
 
 	CFG_CTX_UNLOCK(ctx);
 	return 0;
 
 error:
ec5bc6bb
 	if (block) {
 		/* clean the new block from the cloned arrays */
 		for (	group = cfg_group;
 			group;
 			group = group->next
 		)
 			if (CFG_GROUP_META(block, group)->array
 				&& (CFG_GROUP_META(block, group)->array != CFG_GROUP_META(*cfg_global, group)->array)
 			)
 				shm_free(CFG_GROUP_META(block, group)->array);
 		/* the block can be freed outside of the writer lock */
 	}
 	CFG_WRITER_UNLOCK();
 	if (block)
 		shm_free(block);
a903fdab
 
 error0:
ec5bc6bb
 	CFG_CTX_UNLOCK(ctx);
a903fdab
 
67172188
 	if (child_cb_first) cfg_child_cb_free_list(child_cb_first);
a903fdab
 	if (replaced) shm_free(replaced);
 
 	return -1;
 }
 
 /* drops the not yet committed changes within the context */
 int cfg_rollback(cfg_ctx_t *ctx)
 {
 	cfg_changed_var_t	*changed, *changed2;
 
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_rollback(): context is undefined\n");
 		return -1;
 	}
 
 	if (!cfg_shmized) return 0; /* nothing to do */
 
 	LOG(L_INFO, "INFO: cfg_rollback(): deleting the config changes "
 			"[context=%p]\n",
 			ctx);
 
 	/* the ctx must be locked while reading and writing
 	the list of changed variables */
 	CFG_CTX_LOCK(ctx);
 
 	for (	changed = ctx->changed_first;
 		changed;
 		changed = changed2
 	) {
 		changed2 = changed->next;
 
7d7264e2
 		if (!changed->del_value
 			&& ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 				|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 		) {
e95c0775
 			if (changed->new_val.vp)
 				shm_free(changed->new_val.vp);
a903fdab
 		}
 		shm_free(changed);
 	}
 	ctx->changed_first = NULL;
 
 	CFG_CTX_UNLOCK(ctx);
 
 	return 0;
 }
 
e44fa71d
 /* retrieves the value of a variable
  * Return value:
  *  0 - success
  * -1 - error
  *  1 - variable exists, but it is not readable
  */
5d4d6ae9
 int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
a903fdab
 			void **val, unsigned int *val_type)
 {
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*p;
 	static str	s;	/* we need the value even
 				after the function returns */
5d4d6ae9
 	cfg_group_inst_t	*group_inst;
a903fdab
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_get_by_name(): context is undefined\n");
 		return -1;
 	}
 
 	/* look-up the group and the variable */
 	if (cfg_lookup_var(group_name, var_name, &group, &var))
 		return -1;
 
9a037e7d
 	if (var->def->on_change_cb) {
 		/* The variable cannot be retrieved, because the fixup
 		function may have changed it, and it is better to return
 		an error than an incorrect value */
e44fa71d
 		return 1;
9a037e7d
 	}
 
5d4d6ae9
 	if (group_id) {
 		if (!cfg_local) {
 			LOG(L_ERR, "ERROR: cfg_get_by_name(): Local configuration is missing\n");
 			return -1;
 		}
 		group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
 						group->size,
 						*group_id);
 		if (!group_inst) {
 			LOG(L_ERR, "ERROR: cfg_get_by_name(): local group instance %.*s[%u] is not found\n",
 				group_name->len, group_name->s, *group_id);
 			return -1;
 		}
 		p = group_inst->vars + var->offset;
 
 	} else {
 		/* use the module's handle to access the variable
 		It means that the variable is read from the local config
 		after forking */
 		p = *(group->handle) + var->offset;
 	}
a903fdab
 
 	switch (CFG_VAR_TYPE(var)) {
 	case CFG_VAR_INT:
1b366aa1
 		*val = (void *)(long)*(int *)p;
a903fdab
 		break;
 
 	case CFG_VAR_STRING:
1b366aa1
 		*val = (void *)*(char **)p;
a903fdab
 		break;
 
 	case CFG_VAR_STR:
 		memcpy(&s, p, sizeof(str));
 		*val = (void *)&s;
 		break;
 
 	case CFG_VAR_POINTER:
1b366aa1
 		*val = *(void **)p;
a903fdab
 		break;
 
 	}
 	*val_type = CFG_VAR_TYPE(var);
 
 	return 0;
 }
 
 /* returns the description of a variable */
 int cfg_help(cfg_ctx_t *ctx, str *group_name, str *var_name,
d6b0973a
 			char **ch, unsigned int *input_type)
a903fdab
 {
 	cfg_mapping_t	*var;
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_help(): context is undefined\n");
 		return -1;
 	}
 
 	/* look-up the group and the variable */
 	if (cfg_lookup_var(group_name, var_name, NULL, &var))
 		return -1;
 
 	*ch = var->def->descr;
d6b0973a
 	if (input_type)
 		*input_type = CFG_INPUT_TYPE(var);
a903fdab
 	return 0;
 }
934bcea4
 
 /* return the group name and the cfg structure definition,
  * and moves the handle to the next group
  * Return value:
  *	0: no more group
  *	1: group exists
  */
 int cfg_get_group_next(void **h,
 			str *gname, cfg_def_t **def)
 {
 	cfg_group_t	*group;
 
 	group = (cfg_group_t *)(*h);
 	if (group == NULL) return 0;
 
 	gname->s = group->name;
 	gname->len = group->name_len;
 	(*def) = group->mapping->def;
 
 	(*h) = (void *)group->next;
 	return 1;
 }
 
 /* Initialize the handle for cfg_diff_next() */
 int cfg_diff_init(cfg_ctx_t *ctx,
 		void **h)
 {
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_diff_init(): context is undefined\n");
 		return -1;
 	}
 
 	CFG_CTX_LOCK(ctx);
 	(*h) = (void *)ctx->changed_first;
 
 	return 0;
 }
 
 /* return the pending changes that have not been
  * committed yet
99b680c3
  * return value:
  *	1: valid value is found
  *	0: no more changed value found
  *	-1: error occured 
934bcea4
  */
 int cfg_diff_next(void **h,
ec5bc6bb
 			str *gname, unsigned int **gid, str *vname,
934bcea4
 			void **old_val, void **new_val,
 			unsigned int *val_type)
 {
 	cfg_changed_var_t	*changed;
ec5bc6bb
 	cfg_group_inst_t	*group_inst;
7d7264e2
 	union cfg_var_value	*pval_old, *pval_new;
934bcea4
 	static str	old_s, new_s;	/* we need the value even
 					after the function returns */
 
 	changed = (cfg_changed_var_t *)(*h);
 	if (changed == NULL) return 0;
 
 	gname->s = changed->group->name;
 	gname->len = changed->group->name_len;
ec5bc6bb
 	*gid = (changed->group_id_set ? &changed->group_id : NULL);
934bcea4
 	vname->s = changed->var->def->name;
 	vname->len = changed->var->name_len;
 
 	/* use the module's handle to access the variable
 	It means that the variable is read from the local config
 	after forking */
ec5bc6bb
 	if (!changed->group_id_set) {
7d7264e2
 		pval_old = (union cfg_var_value*)
ec5bc6bb
 				(*(changed->group->handle) + changed->var->offset);
 	} else {
 		if (!cfg_local) {
 			LOG(L_ERR, "ERROR: cfg_diff_next(): Local configuration is missing\n");
99b680c3
 			return -1;
ec5bc6bb
 		}
 		group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, changed->group),
 						changed->group->size,
 						changed->group_id);
 		if (!group_inst) {
 			LOG(L_ERR, "ERROR: cfg_diff_next(): local group instance %.*s[%u] is not found\n",
 				changed->group->name_len, changed->group->name, changed->group_id);
99b680c3
 			return -1;
ec5bc6bb
 		}
7d7264e2
 		pval_old = (union cfg_var_value*)
ec5bc6bb
 				(group_inst->vars + changed->var->offset);
 	}
7d7264e2
 	if (!changed->del_value)
 		pval_new = &changed->new_val;
 	else
 		pval_new = (union cfg_var_value*)
 				(*(changed->group->handle) + changed->var->offset);
934bcea4
 
 	switch (CFG_VAR_TYPE(changed->var)) {
 	case CFG_VAR_INT:
7d7264e2
 		*old_val = (void *)(long)pval_old->vint;
 		*new_val = (void *)(long)pval_new->vint;
934bcea4
 		break;
 
 	case CFG_VAR_STRING:
7d7264e2
 		*old_val = pval_old->vp;
 		*new_val = pval_new->vp;
934bcea4
 		break;
 
 	case CFG_VAR_STR:
7d7264e2
 		old_s=pval_old->vstr;
934bcea4
 		*old_val = (void *)&old_s;
7d7264e2
 		new_s=pval_new->vstr;
934bcea4
 		*new_val = (void *)&new_s;
 		break;
 
 	case CFG_VAR_POINTER:
7d7264e2
 		*old_val = pval_old->vp;
 		*new_val = pval_new->vp;
934bcea4
 		break;
 
 	}
 	*val_type = CFG_VAR_TYPE(changed->var);
 
 	(*h) = (void *)changed->next;
 	return 1;
 }
 
 /* release the handle of cfg_diff_next() */
 void cfg_diff_release(cfg_ctx_t *ctx)
 {
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_diff_release(): context is undefined\n");
 		return;
 	}
 
 	CFG_CTX_UNLOCK(ctx);
 }
28d46c65
 
 /* Add a new instance to an existing group */
 int cfg_add_group_inst(cfg_ctx_t *ctx, str *group_name, unsigned int group_id)
 {
 	cfg_group_t	*group;
 	cfg_block_t	*block = NULL;
 	void		**replaced = NULL;
 	cfg_group_inst_t	*new_array = NULL, *new_inst;
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_add_group_inst(): context is undefined\n");
 		return -1;
 	}
 
 	if (!cfg_shmized) {
eddc94d7
 		/* Add a new variable without any value to
 		the linked list of additional values. This variable
 		will force a new group instance to be created. */
 		return new_add_var(group_name, group_id,  NULL /* var_name */,
 					NULL /* val */, 0 /* type */);
28d46c65
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
 		LOG(L_ERR, "ERROR: cfg_add_group_inst(): group not found\n");
 		return -1;
 	}
 
 	/* make sure that nobody else replaces the global config
 	while the new one is prepared */
 	CFG_WRITER_LOCK();
 	if (cfg_find_group(CFG_GROUP_META(*cfg_global, group),
 							group->size,
 							group_id)
 	) {
 		LOG(L_DBG, "DEBUG: cfg_add_group_inst(): the group instance already exists\n");
 		CFG_WRITER_UNLOCK();
 		return 0; /* not an error */
 	}
 
 	/* clone the global memory block because the additional array can be
 	replaced only together with the block. */
 	if (!(block = cfg_clone_global()))
 		goto error;
 
 	/* Extend the array with a new group instance */
 	if (!(new_array = cfg_extend_array(CFG_GROUP_META(*cfg_global, group), group,
 					group_id,
 					&new_inst))
 	)
 		goto error;
 
 	/* fill in the new group instance with the default data */
 	memcpy(	new_inst->vars,
 		CFG_GROUP_DATA(*cfg_global, group),
5d4d6ae9
 		group->size);
28d46c65
 
 	CFG_GROUP_META(block, group)->array = new_array;
54d8259e
 	CFG_GROUP_META(block, group)->num++;
28d46c65
 
 	if (CFG_GROUP_META(*cfg_global, group)->array) {
 		/* prepare the array of the replaced strings,
 		and replaced group instances,
 		they will be freed when the old block is freed */
 		replaced = (void **)shm_malloc(sizeof(void *) * 2);
 		if (!replaced) {
 			LOG(L_ERR, "ERROR: cfg_add_group_inst(): not enough shm memory\n");
 			goto error;
 		}
 		replaced[0] = CFG_GROUP_META(*cfg_global, group)->array;
 		replaced[1] = NULL;
 	}
 	/* replace the global config with the new one */
 	cfg_install_global(block, replaced, NULL, NULL);
 	CFG_WRITER_UNLOCK();
 
 	LOG(L_INFO, "INFO: cfg_add_group_inst(): "
 		"group instance is added: %.*s[%u]\n",
 		group_name->len, group_name->s,
 		group_id);
 
1dcfdfbd
 	/* Make sure that cfg_set_*() sees the change when
 	 * the function is immediately called after the group
 	 * instance has been added. */
 	cfg_update();
 
28d46c65
 	return 0;
 error:
 	CFG_WRITER_UNLOCK();
 	if (block) cfg_block_free(block);
 	if (new_array) shm_free(new_array);
 	if (replaced) shm_free(replaced);
 
 	LOG(L_ERR, "ERROR: cfg_add_group_inst(): "
 		"Failed to add the group instance: %.*s[%u]\n",
 		group_name->len, group_name->s,
 		group_id);
 
 	return -1;
 }
8e1d174c
 
 /* Delete an instance of a group */
 int cfg_del_group_inst(cfg_ctx_t *ctx, str *group_name, unsigned int group_id)
 {
 	cfg_group_t	*group;
 	cfg_block_t	*block = NULL;
 	void		**replaced = NULL;
 	cfg_group_inst_t	*new_array = NULL, *group_inst;
47daa2e1
 	cfg_mapping_t	*var;
 	int		i, num;
8e1d174c
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_del_group_inst(): context is undefined\n");
 		return -1;
 	}
 
 	if (!cfg_shmized) {
 		/* It makes no sense to delete a group instance that has not
 		been created yet */
 		return -1;
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
 		LOG(L_ERR, "ERROR: cfg_del_group_inst(): group not found\n");
 		return -1;
 	}
 
 	/* make sure that nobody else replaces the global config
 	while the new one is prepared */
 	CFG_WRITER_LOCK();
 	if (!(group_inst = cfg_find_group(CFG_GROUP_META(*cfg_global, group),
 							group->size,
 							group_id))
 	) {
 		LOG(L_DBG, "DEBUG: cfg_del_group_inst(): the group instance does not exist\n");
 		goto error;
 	}
 
 	/* clone the global memory block because the additional array can be
 	replaced only together with the block. */
 	if (!(block = cfg_clone_global()))
 		goto error;
 
 	/* Remove the group instance from the array. */
 	if (cfg_collapse_array(CFG_GROUP_META(*cfg_global, group), group,
 					group_inst,
 					&new_array)
 	)
 		goto error;
 
 	CFG_GROUP_META(block, group)->array = new_array;
 	CFG_GROUP_META(block, group)->num--;
 
 	if (CFG_GROUP_META(*cfg_global, group)->array) {
 		/* prepare the array of the replaced strings,
 		and replaced group instances,
 		they will be freed when the old block is freed */
47daa2e1
 
 		/* count the number of strings that has to be freed */
 		num = 0;
 		for (i = 0; i < group->num; i++) {
 			var = &group->mapping[i];
 			if (CFG_VAR_TEST(group_inst, var)
 				&& ((CFG_VAR_TYPE(var) == CFG_VAR_STRING) || (CFG_VAR_TYPE(var) == CFG_VAR_STR))
 				&& (*(char **)(group_inst->vars + var->offset) != NULL)
 			)
 				num++;
 		}
 
 		replaced = (void **)shm_malloc(sizeof(void *) * (num + 2));
8e1d174c
 		if (!replaced) {
 			LOG(L_ERR, "ERROR: cfg_del_group_inst(): not enough shm memory\n");
 			goto error;
 		}
47daa2e1
 
 		if (num) {
 			/* There was at least one string to free, go though the list again */
 			num = 0;
 			for (i = 0; i < group->num; i++) {
 				var = &group->mapping[i];
 				if (CFG_VAR_TEST(group_inst, var)
 					&& ((CFG_VAR_TYPE(var) == CFG_VAR_STRING) || (CFG_VAR_TYPE(var) == CFG_VAR_STR))
 					&& (*(char **)(group_inst->vars + var->offset) != NULL)
 				) {
 					replaced[num] = *(char **)(group_inst->vars + var->offset);
 					num++;
 				}
 			}
 		}
 
 		replaced[num] = CFG_GROUP_META(*cfg_global, group)->array;
 		replaced[num+1] = NULL;
8e1d174c
 	}
 	/* replace the global config with the new one */
 	cfg_install_global(block, replaced, NULL, NULL);
 	CFG_WRITER_UNLOCK();
 
 	LOG(L_INFO, "INFO: cfg_del_group_inst(): "
 		"group instance is deleted: %.*s[%u]\n",
 		group_name->len, group_name->s,
 		group_id);
 
1dcfdfbd
 	/* Make sure that cfg_set_*() sees the change when
 	 * the function is immediately called after the group
 	 * instance has been deleted. */
 	cfg_update();
 
8e1d174c
 	return 0;
 error:
 	CFG_WRITER_UNLOCK();
 	if (block) cfg_block_free(block);
 	if (new_array) shm_free(new_array);
 	if (replaced) shm_free(replaced);
 
 	LOG(L_ERR, "ERROR: cfg_add_group_inst(): "
 		"Failed to delete the group instance: %.*s[%u]\n",
 		group_name->len, group_name->s,
 		group_id);
 
 	return -1;
 }
92f85d59
 
fc444f6f
 /* Check the existance of a group instance.
  * return value:
  *	1: exists
  *	0: does not exist
  */
 int cfg_group_inst_exists(cfg_ctx_t *ctx, str *group_name, unsigned int group_id)
 {
 	cfg_group_t	*group;
 	cfg_add_var_t	*add_var;
 	int	found;
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	(very very weak security) */
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_group_inst_exists(): context is undefined\n");
 		return 0;
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
 		LOG(L_ERR, "ERROR: cfg_group_inst_exists(): group not found\n");
 		return 0;
 	}
 
 	if (!cfg_shmized) {
 		/* group instances are stored in the additional variable list
 		 * before forking */
 		found = 0;
 		for (	add_var = group->add_var;
 			add_var;
 			add_var = add_var->next
 		)
 			if (add_var->group_id == group_id) {
 				found = 1;
 				break;
 			}
 
 	} else {
 		/* make sure that nobody else replaces the global config meantime */
 		CFG_WRITER_LOCK();
 		found = (cfg_find_group(CFG_GROUP_META(*cfg_global, group),
 								group->size,
 								group_id)
 				!= NULL);
 		CFG_WRITER_UNLOCK();
 	}
 
 	return found;
 }
 
92f85d59
 /* Apply the changes to a group instance as long as the additional variable
  * belongs to the specified group_id. *add_var_p is moved to the next additional
  * variable, and all the consumed variables are freed.
  * This function can be used only during the cfg shmize process.
  * For internal use only!
  */
 int cfg_apply_list(cfg_group_inst_t *ginst, cfg_group_t *group,
 			unsigned int group_id, cfg_add_var_t **add_var_p)
 {
 	cfg_add_var_t	*add_var;
 	cfg_mapping_t	*var;
 	void		*val, *v, *p;
 	str		group_name, var_name, s;
 	char		*old_string;
 
 	group_name.s = group->name;
 	group_name.len = group->name_len;
 	while (*add_var_p && ((*add_var_p)->group_id == group_id)) {
 		add_var = *add_var_p;
 
 		if (add_var->type == 0)
 			goto done; /* Nothing needs to be changed,
 				this additional variable only forces a new
 				group instance to be created. */
 		var_name.s = add_var->name;
 		var_name.len = add_var->name_len;
 
 		if (!(var = cfg_lookup_var2(group, add_var->name, add_var->name_len))) {
 			LOG(L_ERR, "ERROR: cfg_apply_list(): Variable is not found: %.*s.%.*s\n",
 				group->name_len, group->name,
 				add_var->name_len, add_var->name);
 			goto error;
 		}
 
 		/* check whether the variable is read-only */
 		if (var->def->type & CFG_READONLY) {
 			LOG(L_ERR, "ERROR: cfg_apply_list(): variable is read-only\n");
 			goto error;
 		}
 
 		/* The additional variable instances having per-child process callback
 		 * with CFG_CB_ONLY_ONCE flag cannot be rewritten.
 		 * The reason is that such variables typically set global parameters
 		 * as opposed to per-process variables. Hence, it is not possible to set
 		 * the group handle temporary to another block, and then reset it back later. */
 		if (var->def->on_set_child_cb
 			&& var->def->type & CFG_CB_ONLY_ONCE
 		) {
 			LOG(L_ERR, "ERROR: cfg_apply_list(): This variable does not support muliple values.\n");
 			goto error;
 		}
 
 		switch(add_var->type) {
 		case CFG_VAR_INT:
 			val = (void *)(long)add_var->val.i;
 			break;
 		case CFG_VAR_STR:
 			val = (str *)&(add_var->val.s);
 			break;
 		case CFG_VAR_STRING:
 			val = (char *)add_var->val.ch;
 			break;
 		default:
 			LOG(L_ERR, "ERROR: cfg_apply_list(): unsupported variable type: %d\n",
 				add_var->type);
 			goto error;
 		}
 		/* check whether we have to convert the type */
 		if (convert_val(add_var->type, val, CFG_INPUT_TYPE(var), &v))
 			goto error;
 
 		if ((CFG_INPUT_TYPE(var) == CFG_INPUT_INT) 
 		&& (var->def->min || var->def->max)) {
 			/* perform a simple min-max check for integers */
 			if (((int)(long)v < var->def->min)
 			|| ((int)(long)v > var->def->max)) {
 				LOG(L_ERR, "ERROR: cfg_apply_list(): integer value is out of range\n");
 				goto error;
 			}
 		}
 
 		if (var->def->on_change_cb) {
 			/* Call the fixup function.
 			The handle can point to the variables of the group instance. */
 			if (var->def->on_change_cb(ginst->vars,
 							&group_name,
 							&var_name,
 							&v) < 0) {
 				LOG(L_ERR, "ERROR: cfg_apply_list(): fixup failed\n");
 				goto error;
 			}
 		}
 
 		p = ginst->vars + var->offset;
 		old_string = NULL;
 		/* set the new value */
 		switch (CFG_VAR_TYPE(var)) {
 		case CFG_VAR_INT:
 			*(int *)p = (int)(long)v;
 			break;
 
 		case CFG_VAR_STRING:
 			/* clone the string to shm mem */
 			s.s = v;
 			s.len = (s.s) ? strlen(s.s) : 0;
 			if (cfg_clone_str(&s, &s)) goto error;
 			old_string = *(char **)p;
 			*(char **)p = s.s;
 			break;
 
 		case CFG_VAR_STR:
 			/* clone the string to shm mem */
 			s = *(str *)v;
 			if (cfg_clone_str(&s, &s)) goto error;
 			old_string = *(char **)p;
 			memcpy(p, &s, sizeof(str));
 			break;
 
 		case CFG_VAR_POINTER:
 			*(void **)p = v;
 			break;
 
 		}
 		if (CFG_VAR_TEST_AND_SET(ginst, var) && old_string)
 			shm_free(old_string); /* the string was already in shm memory,
 					it needs to be freed.
 					This can happen when the same variable is set
 					multiple times before forking. */
 
 		if (add_var->type == CFG_VAR_INT)
 			LOG(L_INFO, "INFO: cfg_apply_list(): %.*s[%u].%.*s "
 				"has been set to %d\n",
 				group_name.len, group_name.s,
 				group_id,
 				var_name.len, var_name.s,
 				(int)(long)val);
 
 		else if (add_var->type == CFG_VAR_STRING)
 			LOG(L_INFO, "INFO: cfg_apply_list(): %.*s[%u].%.*s "
 				"has been set to \"%s\"\n",
 				group_name.len, group_name.s,
 				group_id,
 				var_name.len, var_name.s,
 				(char *)val);
 
 		else /* str type */
 			LOG(L_INFO, "INFO: cfg_apply_list(): %.*s[%u].%.*s "
 				"has been set to \"%.*s\"\n",
 				group_name.len, group_name.s,
 				group_id,
 				var_name.len, var_name.s,
 				((str *)val)->len, ((str *)val)->s);
 
 		convert_val_cleanup();
 
 done:
 		*add_var_p = add_var->next;
 
 		if ((add_var->type == CFG_VAR_STR) && add_var->val.s.s)
 			pkg_free(add_var->val.s.s);
 		else if ((add_var->type == CFG_VAR_STRING) && add_var->val.ch)
 			pkg_free(add_var->val.ch);
 		pkg_free(add_var);
 	}
 	return 0;
 
 error:
 	LOG(L_ERR, "ERROR: cfg_apply_list(): Failed to set the value for: %.*s[%u].%.*s\n",
 		group->name_len, group->name,
 		group_id,
 		add_var->name_len, add_var->name);
 	convert_val_cleanup();
 	return -1;
 }