src/core/cfg/cfg_ctx.c
a903fdab
 /*
  * Copyright (C) 2007 iptelorg GmbH
  *
1fff03a2
  * This file is part of Kamailio, a free SIP server.
a903fdab
  *
1fff03a2
  * Kamailio 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
  *
1fff03a2
  * Kamailio 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
9e1ff448
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
a903fdab
  *
  */
 
 #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
08496ed4
 	 * Better to use shm mem, because 'changed' and 'lock'
 	 * must be in shm mem anyway */
a903fdab
 	ctx = (cfg_ctx_t *)shm_malloc(sizeof(cfg_ctx_t));
 	if (!ctx) {
e3ecad34
 		SHM_MEM_ERROR;
30feb9b6
 		return -1;
a903fdab
 	}
 	memset(ctx, 0, sizeof(cfg_ctx_t));
 	if (lock_init(&ctx->lock) == 0) {
08496ed4
 		LM_ERR("failed to init lock\n");
a903fdab
 		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;
 
08496ed4
 		for (group = cfg_group;
 				group;
 				group = group->next
 				) {
140137c9
 			/* dynamic groups are not ready, the callback
08496ed4
 			 * 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;
 
08496ed4
 	for (ctx = cfg_ctx_list;
 			ctx;
 			ctx = ctx2
 			) {
a903fdab
 		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
 
08496ed4
 	for (ctx = cfg_ctx_list;
 			ctx;
 			ctx = ctx->next
 			)
a903fdab
 		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,
08496ed4
 		unsigned int var_type, void **new_val)
a903fdab
 {
 	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)) {
08496ed4
 		case CFG_INPUT_INT:
 			if (val_type == CFG_VAR_INT) {
 				*new_val = val;
 				break;
a903fdab
 
08496ed4
 			} else if (val_type == CFG_VAR_STRING) {
 				if (!val || (((char *)val)[0] == '\0')) {
 					LM_ERR("cannot convert NULL string value to integer\n");
 					return -1;
 				}
 				*new_val = (void *)(long)strtol((char *)val, &end, 10);
 				if (*end != '\0') {
 					LM_ERR("cannot convert string to integer '%s'\n",
 							(char *)val);
 					return -1;
 				}
 				break;
e5cba078
 
08496ed4
 			} else if (val_type == CFG_VAR_STR) {
 				if (!((str *)val)->len || !((str *)val)->s) {
 					LM_ERR("cannot convert NULL str value to integer\n");
 					return -1;
 				}
 				if (str2sint((str *)val, &i)) {
 					LM_ERR("cannot convert string to integer '%.*s'\n",
 							((str *)val)->len, ((str *)val)->s);
 					return -1;
 				}
 				*new_val = (void *)(long)i;
 				break;
e5cba078
 			}
08496ed4
 			goto error;
e5cba078
 
08496ed4
 		case CFG_INPUT_STRING:
 			if (val_type == CFG_VAR_INT) {
 				buf[snprintf(buf, sizeof(buf)-1, "%ld", (long)val)] = '\0';
 				*new_val = buf;
 				break;
e5cba078
 
08496ed4
 			} else if (val_type == CFG_VAR_STRING) {
 				*new_val = val;
 				break;
e5cba078
 
08496ed4
 			} else if (val_type == CFG_VAR_STR) {
 				if (!((str *)val)->s) {
 					*new_val = NULL;
 					break;
 				}
 				/* 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) {
e3ecad34
 					PKG_MEM_ERROR;
08496ed4
 					return -1;
 				}
 				memcpy(temp_string, ((str *)val)->s, ((str *)val)->len);
 				temp_string[((str *)val)->len] = '\0';
 				*new_val = (void *)temp_string;
c55d3c7f
 				break;
e5cba078
 
08496ed4
 			}
 			goto error;
e5cba078
 
08496ed4
 		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;
e5cba078
 
08496ed4
 			} else if (val_type == CFG_VAR_STRING) {
 				s.s = (char *)val;
 				s.len = (s.s) ? strlen(s.s) : 0;
 				*new_val = (void *)&s;
 				break;
e5cba078
 
08496ed4
 			} else if (val_type == CFG_VAR_STR) {
 				*new_val = val;
 				break;
 			}
 			goto error;
a903fdab
 	}
 
 	return 0;
 
 error:
08496ed4
 	LM_ERR("got a value with type %u, but expected %u\n",
a903fdab
 			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)) {
 
08496ed4
 		case CFG_VAR_INT:
 			return sizeof(int);
6a44d0bd
 
08496ed4
 		case CFG_VAR_STRING:
 			return sizeof(char *);
6a44d0bd
 
08496ed4
 		case CFG_VAR_STR:
 			return sizeof(str);
6a44d0bd
 
08496ed4
 		case CFG_VAR_POINTER:
 			return sizeof(void *);
6a44d0bd
 
08496ed4
 		default:
 			LM_CRIT("unknown type: %u\n", CFG_VAR_TYPE(var));
 			return 0;
6a44d0bd
 	}
 }
 
 /* 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,
08496ed4
 		cfg_group_t *group, cfg_mapping_t *var, char *new_val,
 		int clone)
6a44d0bd
 {
 	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
08496ed4
 				+ (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,
08496ed4
 						(char *)meta->array,
 						(char *)ginst);
6a44d0bd
 				/* re-link the array to the meta-data */
 				meta->array = array;
 				clone_done = 1;
 			}
335b40a4
 			if(ginst->vars + var->offset) {
 				memcpy(ginst->vars + var->offset, new_val, cfg_var_size(var));
 			} else {
 				LM_ERR("invalid variable offset\n");
 			}
6a44d0bd
 		}
 	}
 	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,
08496ed4
 		void *val, unsigned int val_type)
a903fdab
 {
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
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
a903fdab
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		return -1;
 	}
 
7d7264e2
 	if ((val_type == CFG_VAR_UNSET) && !group_id) {
08496ed4
 		LM_ERR("Only group instance values can be deleted\n");
7d7264e2
 		return -1;
 	}
 
6a44d0bd
 	if (group_id && !cfg_shmized) {
08496ed4
 		/* 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))
08496ed4
 					&& (group->dynamic == CFG_GROUP_DYNAMIC))
ca88d956
 				return cfg_set_script_var(group, var_name, val, val_type);
 		}
cabd961c
 		return 1;
ca88d956
 	}
08496ed4
 
b9c10056
 	/* check whether the variable is read-only */
 	if (var->def->type & CFG_READONLY) {
08496ed4
 		LM_ERR("variable is read-only\n");
b9c10056
 		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
08496ed4
 			&& var->def->on_set_child_cb
 			&& var->def->type & CFG_CB_ONLY_ONCE ) {
 		LM_ERR("This variable does not support muliple values.\n");
6a44d0bd
 		goto error0;
 	}
 
a903fdab
 	/* check whether we have to convert the type */
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
08496ed4
 			&& convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
a903fdab
 		goto error0;
08496ed4
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
08496ed4
 			&& (CFG_INPUT_TYPE(var) == CFG_INPUT_INT)
 			&& (var->def->min || var->def->max)) {
29787f15
 		/* perform a simple min-max check for integers */
 		if (((int)(long)v < var->def->min)
08496ed4
 				|| ((int)(long)v > var->def->max)) {
 			LM_ERR("integer value is out of range\n");
29787f15
 			goto error0;
 		}
 	}
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
08496ed4
 			&& var->def->on_change_cb) {
a903fdab
 		/* Call the fixup function.
08496ed4
 		 * 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) {
08496ed4
 				LM_ERR("Local configuration is missing\n");
6a44d0bd
 				goto error0;
 			}
 			group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
08496ed4
 					group->size,
 					*group_id);
6a44d0bd
 			if (!group_inst) {
08496ed4
 				LM_ERR("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,
08496ed4
 					group_name,
 					var_name,
 					&v) < 0) {
 			LM_ERR("fixup failed\n");
a903fdab
 			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,
08496ed4
 		 * 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,
08496ed4
 				var->def->on_set_child_cb,
 				var->def->type);
a0b44e92
 		if (!child_cb) {
e3ecad34
 			SHM_MEM_ERROR;
a0b44e92
 			goto error0;
a903fdab
 		}
a0b44e92
 	}
a903fdab
 
a0b44e92
 	if (cfg_shmized) {
a903fdab
 		/* make sure that nobody else replaces the global config
08496ed4
 		 * while the new one is prepared */
a903fdab
 		CFG_WRITER_LOCK();
 
6a44d0bd
 		if (group_id) {
 			group_inst = cfg_find_group(CFG_GROUP_META(*cfg_global, group),
08496ed4
 					group->size,
 					*group_id);
6a44d0bd
 			if (!group_inst) {
08496ed4
 				LM_ERR("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();
08496ed4
 				LM_DBG("The variable is not set\n");
7d7264e2
 				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
08496ed4
 			 * 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,
08496ed4
 						(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
08496ed4
 		 * The handle either points to group->vars, or to the
 		 * shared memory block (dynamic group) */
140137c9
 		p = *(group->handle) + var->offset;
a903fdab
 	}
 
7d7264e2
 	if (val_type != CFG_VAR_UNSET) {
 		/* set the new value */
 		switch (CFG_VAR_TYPE(var)) {
08496ed4
 			case CFG_VAR_INT:
 				*(int *)p = (int)(long)v;
 				break;
a903fdab
 
08496ed4
 			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
 
08496ed4
 			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
 
08496ed4
 			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,
08496ed4
 								* it cannot be freed */
7d7264e2
 	} else {
 		/* copy the default value to the group instance */
 		if ((CFG_VAR_TYPE(var) == CFG_VAR_STRING)
08496ed4
 				|| (CFG_VAR_TYPE(var) == CFG_VAR_STR))
7d7264e2
 			old_string = *(char **)p;
08496ed4
 		memcpy(p, CFG_GROUP_DATA(*cfg_global, group) + var->offset, cfg_var_size(var));
7d7264e2
 
 		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
08496ed4
 			 * need to be also updated */
8e1d174c
 			if (cfg_update_defaults(CFG_GROUP_META(block ? block : *cfg_global, group),
6a44d0bd
 						group, var, p,
8e1d174c
 						block ? 1 : 0) /* clone if needed */
08496ed4
 					)
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,
08496ed4
 			 * and replaced group instances,
 			 * 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) {
e3ecad34
 				SHM_MEM_ERROR;
a903fdab
 				goto error;
 			}
27580846
 			i = 0;
 			if (old_string) {
 				replaced[i] = old_string;
 				i++;
 			}
08496ed4
 			if (new_array) {
27580846
 				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
08496ed4
 		 * to shmize it again */
a903fdab
 		var->flag |= cfg_var_shmized;
a0b44e92
 
 		/* the global config does not have to be replaced,
08496ed4
 		 * 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)
08496ed4
 		LM_INFO("%.*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)
08496ed4
 		LM_INFO("%.*s.%.*s has been changed to \"%s\"\n",
 				group_name->len, group_name->s,
 				var_name->len, var_name->s,
 				(char *)val);
a903fdab
 
7d7264e2
 	else if (val_type == CFG_VAR_STR)
08496ed4
 		LM_INFO("%.*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)
08496ed4
 		LM_INFO("%.*s.%.*s has been deleted\n",
 				group_name->len, group_name->s,
 				var_name->len, var_name->s);
7d7264e2
 
 	else
08496ed4
 		LM_INFO("%.*s.%.*s has been changed\n",
 				group_name->len, group_name->s,
 				var_name->len, var_name->s);
7d7264e2
 
1e3ac66d
 	if (group_id)
08496ed4
 		LM_INFO("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:
08496ed4
 	LM_ERR("failed to set the variable: %.*s.%.*s\n",
a903fdab
 			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 */
08496ed4
 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,
08496ed4
 			(void *)(long)val, CFG_VAR_INT);
a903fdab
 }
 
 /* wrapper function for cfg_set_now */
08496ed4
 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,
08496ed4
 			(void *)val, CFG_VAR_STRING);
a903fdab
 }
 
e5cba078
 /* wrapper function for cfg_set_now */
08496ed4
 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,
08496ed4
 			(void *)val, CFG_VAR_STR);
a903fdab
 }
 
7d7264e2
 /* Delete a variable from the group instance.
  * wrapper function for cfg_set_now */
08496ed4
 int cfg_del_now(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id,
 		str *var_name)
7d7264e2
 {
 	return cfg_set_now(ctx, group_name, group_id, var_name,
08496ed4
 			NULL, CFG_VAR_UNSET);
7d7264e2
 }
 
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
  */
08496ed4
 int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id,
 		str *var_name, void *val, unsigned int val_type)
a903fdab
 {
 	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
08496ed4
 		 * point in registering the change and committing it later */
6a44d0bd
 		return cfg_set_now(ctx, group_name, group_id, var_name,
08496ed4
 				val, val_type);
a903fdab
 
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		return -1;
 	}
 
7d7264e2
 	if ((val_type == CFG_VAR_UNSET) && !group_id) {
08496ed4
 		LM_ERR("Only group instance values can be deleted\n");
7d7264e2
 		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) {
08496ed4
 		LM_ERR("variable is read-only\n");
b9c10056
 		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
08496ed4
 			&& var->def->on_set_child_cb
 			&& var->def->type & CFG_CB_ONLY_ONCE) {
 		LM_ERR("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)
08496ed4
 			&& convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
a903fdab
 		goto error0;
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET)
08496ed4
 			&& (CFG_INPUT_TYPE(var) == CFG_INPUT_INT)
 			&& (var->def->min || var->def->max)) {
29787f15
 		/* perform a simple min-max check for integers */
 		if (((int)(long)v < var->def->min)
08496ed4
 				|| ((int)(long)v > var->def->max)) {
 			LM_ERR("integer value is out of range\n");
1813606a
 			goto error0;
29787f15
 		}
 	}
 
a903fdab
 	/* the ctx must be locked while reading and writing
08496ed4
 	 * the list of changed variables */
a903fdab
 	CFG_CTX_LOCK(ctx);
 
7d7264e2
 	if ((val_type != CFG_VAR_UNSET) && var->def->on_change_cb) {
a903fdab
 		/* The fixup function must see also the
08496ed4
 		 * 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) {
08496ed4
 				LM_ERR("Local configuration is missing\n");
ec5bc6bb
 				goto error;
 			}
 			group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
08496ed4
 					group->size,
 					*group_id);
ec5bc6bb
 			if (!group_inst) {
08496ed4
 				LM_ERR("local group instance %.*s[%u] is not found\n",
 						group_name->len, group_name->s, *group_id);
ec5bc6bb
 				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) {
e3ecad34
 				PKG_MEM_ERROR;
a903fdab
 				goto error;
 			}
 			temp_handle_created = 1;
ec5bc6bb
 			memcpy(temp_handle, var_block, group->size);
a903fdab
 
 			/* apply the changes */
 			for (	changed = ctx->changed_first;
08496ed4
 					changed;
 					changed = changed->next
 				) {
ec5bc6bb
 				if (changed->group != group)
 					continue;
 				if ((!group_id && !changed->group_id_set) /* default values */
08496ed4
 						|| (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 */
 						)
ec5bc6bb
 					memcpy(	temp_handle + changed->var->offset,
08496ed4
 							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;
 		}
08496ed4
 
a903fdab
 		if (var->def->on_change_cb(temp_handle,
08496ed4
 					group_name,
 					var_name,
 					&v) < 0) {
 			LM_ERR("fixup failed\n");
a903fdab
 			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) {
e3ecad34
 		SHM_MEM_ERROR;
a903fdab
 		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
 
08496ed4
 			case CFG_VAR_INT:
 				changed->new_val.vint = (int)(long)v;
 				break;
a903fdab
 
08496ed4
 			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
 
08496ed4
 			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
 
08496ed4
 			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
08496ed4
 	 * belongs to the same group */
ec5bc6bb
 	for (	changed_p = &ctx->changed_first;
08496ed4
 			*changed_p && ((*changed_p)->group != changed->group);
 			changed_p = &(*changed_p)->next);
ec5bc6bb
 	/* try to find the group instance, and move changed_p to the end of
08496ed4
 	 * the instance. */
ec5bc6bb
 	for (	;
08496ed4
 			*changed_p
ec5bc6bb
 			&& ((*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)));
08496ed4
 			changed_p = &(*changed_p)->next);
ec5bc6bb
 	/* 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)
08496ed4
 		LM_INFO("%.*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)
08496ed4
 		LM_INFO("%.*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);
a903fdab
 
7d7264e2
 	else if (val_type == CFG_VAR_STR)
08496ed4
 		LM_INFO("%.*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)
08496ed4
 		LM_INFO("%.*s.%.*s is going to be deleted "
 				"[context=%p]\n",
 				group_name->len, group_name->s,
 				var_name->len, var_name->s,
 				ctx);
7d7264e2
 
 	else
08496ed4
 		LM_INFO("%.*s.%.*s is going to be changed "
 				"[context=%p]\n",
 				group_name->len, group_name->s,
 				var_name->len, var_name->s,
 				ctx);
7d7264e2
 
ec5bc6bb
 	if (group_id)
08496ed4
 		LM_INFO("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:
08496ed4
 	LM_ERR("failed to set the variable: %.*s.%.*s\n",
a903fdab
 			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 */
08496ed4
 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,
08496ed4
 			(void *)(long)val, CFG_VAR_INT);
a903fdab
 }
 
 /* wrapper function for cfg_set_delayed */
08496ed4
 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,
08496ed4
 			(void *)val, CFG_VAR_STRING);
a903fdab
 }
 
e5cba078
 /* wrapper function for cfg_set_delayed */
08496ed4
 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,
08496ed4
 			(void *)val, CFG_VAR_STR);
e5cba078
 }
 
7d7264e2
 /* Delete a variable from the group instance.
  * wrapper function for cfg_set_delayed */
08496ed4
 int cfg_del_delayed(cfg_ctx_t *ctx, str *group_name,
 		unsigned int *group_id, str *var_name)
7d7264e2
 {
 	return cfg_set_delayed(ctx, group_name, group_id, var_name,
08496ed4
 			NULL, CFG_VAR_UNSET);
7d7264e2
 }
 
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) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		return -1;
 	}
 
 	if (!cfg_shmized) return 0; /* nothing to do */
 
 	/* the ctx must be locked while reading and writing
08496ed4
 	 * the list of changed variables */
a903fdab
 	CFG_CTX_LOCK(ctx);
 
 	/* is there any change? */
 	if (!ctx->changed_first) goto done;
 
ec5bc6bb
 	/* Count the number of replaced strings,
08496ed4
 	 * and replaced group arrays.
 	 * Prepare the linked list of per-child process
 	 * callbacks, that will be added to the global list. */
3deee5be
 	for(changed = ctx->changed_first, group = NULL; 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. */
3deee5be
 		if((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 				|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
a903fdab
 			replaced_num++;
 
ec5bc6bb
 		/* See the above comments for strings */
 		if (group != changed->group) {
 			replaced_num++;
 			group = changed->group;
 		}
a903fdab
 
08496ed4
 		if (changed->group && !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) {
e3ecad34
 			SHM_MEM_ERROR;
ec5bc6bb
 			goto error0;
c55d3c7f
 		}
 		memset(replaced, 0 , size);
3deee5be
 	} else {
 		/* no replacement */
 		LM_INFO("commit operation executed without having changes\n");
 		return 0;
a903fdab
 	}
 
 	/* make sure that nobody else replaces the global config
08496ed4
 	 * while the new one is prepared */
a903fdab
 	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.
08496ed4
 	 * 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;
3deee5be
 	for(changed = ctx->changed_first, group = NULL; /* group points to the
08496ed4
 													 * last group array that
 													 * has been cloned */
3deee5be
 			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;
08496ed4
 				if (!(CFG_GROUP_META(block, group)->array =
 							cfg_clone_array(CFG_GROUP_META(*cfg_global, group), group))
 						) {
 					LM_ERR("group array cannot be cloned for %.*s[%u]\n",
 							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 */
 			}
bf7b6c25
 			if (group && (!group_inst || (group_inst->id != changed->group_id))) {
ec5bc6bb
 				group_inst = cfg_find_group(CFG_GROUP_META(block, group),
08496ed4
 						group->size,
 						changed->group_id);
ec5bc6bb
 			}
bf7b6c25
 			if (group && !group_inst) {
08496ed4
 				LM_ERR("global group instance %.*s[%u] is not found\n",
 						group->name_len, group->name, changed->group_id);
ec5bc6bb
 				goto error;
 			}
 			p = group_inst->vars + changed->var->offset;
 		}
335b40a4
 		if(p==NULL) {
 			LM_ERR("failed to resolve valid variable offset\n");
 			goto error;
 		}
ec5bc6bb
 
08496ed4
 		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))
 					|| !changed->group_id_set)
 				&& ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 					|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 				) {
1b366aa1
 			replaced[replaced_num] = *(char **)p;
c55d3c7f
 			if (replaced[replaced_num])
 				replaced_num++;
 			/* else do not increase replaced_num, because
08496ed4
 			 * the cfg_block_free() will stop at the first
 			 * NULL value */
a903fdab
 		}
 
7d7264e2
 		if (!changed->del_value)
 			memcpy(	p,
08496ed4
 					changed->new_val.vraw,
 					cfg_var_size(changed->var));
7d7264e2
 		else
 			memcpy(	p,
08496ed4
 					CFG_GROUP_DATA(block, changed->group) + changed->var->offset,
 					cfg_var_size(changed->var));
7d7264e2
 
ec5bc6bb
 
 		if (!changed->group_id_set) {
 			/* the default value is changed, the copies of this value
08496ed4
 			 * need to be also updated */
ec5bc6bb
 			if (cfg_update_defaults(CFG_GROUP_META(block, changed->group),
 						changed->group, changed->var, p,
 						(group != changed->group)) /* clone if the array
08496ed4
 													* 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)
 					) {
ec5bc6bb
 				/* 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;
08496ed4
 			changed;
 			changed = changed2
 		) {
a903fdab
 		changed2 = changed->next;
 		shm_free(changed);
 	}
 	ctx->changed_first = NULL;
 
 done:
08496ed4
 	LM_INFO("config changes have been applied [context=%p]\n",
a903fdab
 			ctx);
 
 	CFG_CTX_UNLOCK(ctx);
 	return 0;
 
 error:
ec5bc6bb
 	if (block) {
 		/* clean the new block from the cloned arrays */
08496ed4
 		for (group = cfg_group;
 				group;
 				group = group->next
 				)
ec5bc6bb
 			if (CFG_GROUP_META(block, group)->array
08496ed4
 					&& (CFG_GROUP_META(block, group)->array
 						!= CFG_GROUP_META(*cfg_global, group)->array)
 					)
ec5bc6bb
 				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) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		return -1;
 	}
 
 	if (!cfg_shmized) return 0; /* nothing to do */
 
08496ed4
 	LM_INFO("deleting the config changes [context=%p]\n",
a903fdab
 			ctx);
 
 	/* the ctx must be locked while reading and writing
08496ed4
 	 * the list of changed variables */
a903fdab
 	CFG_CTX_LOCK(ctx);
 
08496ed4
 	for (changed = ctx->changed_first;
 			changed;
 			changed = changed2
 			) {
a903fdab
 		changed2 = changed->next;
 
7d7264e2
 		if (!changed->del_value
08496ed4
 				&& ((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
  */
15aaff16
 int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id,
 		str *var_name, void **val, unsigned int *val_type)
a903fdab
 {
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*p;
 	static str	s;	/* we need the value even
08496ed4
 					 * after the function returns */
5d4d6ae9
 	cfg_group_inst_t	*group_inst;
a903fdab
 
 	/* verify the context even if we do not need it now
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
a903fdab
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		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
08496ed4
 		 * 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) {
08496ed4
 			LM_ERR("Local configuration is missing\n");
5d4d6ae9
 			return -1;
 		}
 		group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
08496ed4
 				group->size,
 				*group_id);
5d4d6ae9
 		if (!group_inst) {
08496ed4
 			LM_ERR("local group instance %.*s[%u] is not found\n",
 					group_name->len, group_name->s, *group_id);
5d4d6ae9
 			return -1;
 		}
 		p = group_inst->vars + var->offset;
 
 	} else {
 		/* use the module's handle to access the variable
08496ed4
 		 * It means that the variable is read from the local config
 		 * after forking */
5d4d6ae9
 		p = *(group->handle) + var->offset;
 	}
a903fdab
 
 	switch (CFG_VAR_TYPE(var)) {
08496ed4
 		case CFG_VAR_INT:
 			*val = (void *)(long)*(int *)p;
 			break;
a903fdab
 
08496ed4
 		case CFG_VAR_STRING:
 			*val = (void *)*(char **)p;
 			break;
a903fdab
 
08496ed4
 		case CFG_VAR_STR:
 			memcpy(&s, p, sizeof(str));
 			*val = (void *)&s;
 			break;
a903fdab
 
08496ed4
 		case CFG_VAR_POINTER:
 			*val = *(void **)p;
 			break;
a903fdab
 
 	}
 	*val_type = CFG_VAR_TYPE(var);
 
 	return 0;
 }
 
89334293
 /* retrieves the default value of a variable
  * Return value:
  *  0 - success
  * -1 - error
  *  1 - variable exists, but it is not readable
  */
15aaff16
 int cfg_get_default_value_by_name(cfg_ctx_t *ctx, str *group_name,
 		unsigned int *group_id, str *var_name, void **val,
 		unsigned int *val_type)
89334293
 {
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void	*p;
 	static str	s;	/* we need the value even
e6c9474e
 					 * after the function returns */
89334293
 
 	/* verify the context even if we do not need it now
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
89334293
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
89334293
 		return -1;
 	}
 
 	/* look-up the group and the variable */
15aaff16
 	if (cfg_lookup_var(group_name, var_name, &group, &var)) {
89334293
 		return -1;
15aaff16
 	} else {
 		/* if variables exist then prevents resetting the read-only ones */
 		if(var->def->type & CFG_READONLY)
89334293
 			return -1;
 	}
 
 	if (var->def->on_change_cb) {
 		/* The variable cannot be retrieved, because the fixup
08496ed4
 		 * function may have changed it, and it is better to return
 		 * an error than an incorrect value */
89334293
 		return 1;
 	}
 
15aaff16
 	/* use the module's orig_handle to access the default registered value of
 	 * the variable for any group*/
89334293
 	p = (group->orig_handle) + var->offset;
 
 	switch (CFG_VAR_TYPE(var)) {
 		case CFG_VAR_INT:
 			*val = (void *)(long)*(int *)p;
 			break;
 
 		case CFG_VAR_STRING:
 			*val = (void *)*(char **)p;
 			break;
 
 		case CFG_VAR_STR:
 			memcpy(&s, p, sizeof(str));
 			*val = (void *)&s;
 			break;
 
 		case CFG_VAR_POINTER:
 			*val = *(void **)p;
 			break;
 
 	}
 	*val_type = CFG_VAR_TYPE(var);
 
 	return 0;
 }
 
 
a903fdab
 /* returns the description of a variable */
 int cfg_help(cfg_ctx_t *ctx, str *group_name, str *var_name,
08496ed4
 		char **ch, unsigned int *input_type)
a903fdab
 {
 	cfg_mapping_t	*var;
 
 	/* verify the context even if we do not need it now
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
a903fdab
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
a903fdab
 		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,
08496ed4
 		str *gname, cfg_def_t **def)
934bcea4
 {
 	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) {
08496ed4
 		LM_ERR("context is undefined\n");
934bcea4
 		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
08496ed4
  *	-1: error occurred
934bcea4
  */
 int cfg_diff_next(void **h,
08496ed4
 		str *gname, unsigned int **gid, str *vname,
 		void **old_val, void **new_val,
 		unsigned int *val_type)
934bcea4
 {
 	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
08496ed4
 								 * after the function returns */
934bcea4
 
 	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
08496ed4
 	 * 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*)
08496ed4
 			(*(changed->group->handle) + changed->var->offset);
ec5bc6bb
 	} else {
 		if (!cfg_local) {
08496ed4
 			LM_ERR("Local configuration is missing\n");
99b680c3
 			return -1;
ec5bc6bb
 		}
 		group_inst = cfg_find_group(CFG_GROUP_META(cfg_local, changed->group),
08496ed4
 				changed->group->size,
 				changed->group_id);
ec5bc6bb
 		if (!group_inst) {
08496ed4
 			LM_ERR("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*)
08496ed4
 			(group_inst->vars + changed->var->offset);
ec5bc6bb
 	}
7d7264e2
 	if (!changed->del_value)
 		pval_new = &changed->new_val;
 	else
 		pval_new = (union cfg_var_value*)
08496ed4
 			(*(changed->group->handle) + changed->var->offset);
934bcea4
 
 	switch (CFG_VAR_TYPE(changed->var)) {
08496ed4
 		case CFG_VAR_INT:
 			*old_val = (void *)(long)pval_old->vint;
 			*new_val = (void *)(long)pval_new->vint;
 			break;
934bcea4
 
08496ed4
 		case CFG_VAR_STRING:
 			*old_val = pval_old->vp;
 			*new_val = pval_new->vp;
 			break;
934bcea4
 
08496ed4
 		case CFG_VAR_STR:
 			old_s=pval_old->vstr;
 			*old_val = (void *)&old_s;
 			new_s=pval_new->vstr;
 			*new_val = (void *)&new_s;
 			break;
934bcea4
 
08496ed4
 		case CFG_VAR_POINTER:
 			*old_val = pval_old->vp;
 			*new_val = pval_new->vp;
 			break;
934bcea4
 
 	}
 	*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) {
08496ed4
 		LM_ERR("context is undefined\n");
934bcea4
 		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
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
28d46c65
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
28d46c65
 		return -1;
 	}
 
 	if (!cfg_shmized) {
eddc94d7
 		/* Add a new variable without any value to
08496ed4
 		 * the linked list of additional values. This variable
 		 * will force a new group instance to be created. */
eddc94d7
 		return new_add_var(group_name, group_id,  NULL /* var_name */,
08496ed4
 				NULL /* val */, 0 /* type */);
28d46c65
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
08496ed4
 		LM_ERR("group not found\n");
28d46c65
 		return -1;
 	}
 
 	/* make sure that nobody else replaces the global config
08496ed4
 	 * while the new one is prepared */
28d46c65
 	CFG_WRITER_LOCK();
 	if (cfg_find_group(CFG_GROUP_META(*cfg_global, group),
08496ed4
 				group->size,
 				group_id)
 			) {
 		LM_DBG("the group instance already exists\n");
28d46c65
 		CFG_WRITER_UNLOCK();
 		return 0; /* not an error */
 	}
 
 	/* clone the global memory block because the additional array can be
08496ed4
 	 * replaced only together with the block. */
28d46c65
 	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))
08496ed4
 			)
28d46c65
 		goto error;
 
 	/* fill in the new group instance with the default data */
 	memcpy(	new_inst->vars,
08496ed4
 			CFG_GROUP_DATA(*cfg_global, group),
 			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,
08496ed4
 		 * and replaced group instances,
 		 * they will be freed when the old block is freed */
28d46c65
 		replaced = (void **)shm_malloc(sizeof(void *) * 2);
 		if (!replaced) {
e3ecad34
 			SHM_MEM_ERROR;
28d46c65
 			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();
 
08496ed4
 	LM_INFO("group instance is added: %.*s[%u]\n",
 			group_name->len, group_name->s,
 			group_id);
28d46c65
 
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);
 
08496ed4
 	LM_ERR("Failed to add the group instance: %.*s[%u]\n",
 			group_name->len, group_name->s,
 			group_id);
28d46c65
 
 	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
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
8e1d174c
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
8e1d174c
 		return -1;
 	}
 
 	if (!cfg_shmized) {
 		/* It makes no sense to delete a group instance that has not
08496ed4
 		 * been created yet */
8e1d174c
 		return -1;
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
08496ed4
 		LM_ERR("group not found\n");
8e1d174c
 		return -1;
 	}
 
 	/* make sure that nobody else replaces the global config
08496ed4
 	 * while the new one is prepared */
8e1d174c
 	CFG_WRITER_LOCK();
 	if (!(group_inst = cfg_find_group(CFG_GROUP_META(*cfg_global, group),
08496ed4
 					group->size,
 					group_id))
 			) {
 		LM_DBG("the group instance does not exist\n");
8e1d174c
 		goto error;
 	}
 
 	/* clone the global memory block because the additional array can be
08496ed4
 	 * replaced only together with the block. */
8e1d174c
 	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,
08496ed4
 				group_inst,
 				&new_array))
8e1d174c
 		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,
08496ed4
 		 * 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)
08496ed4
 					&& ((CFG_VAR_TYPE(var) == CFG_VAR_STRING)
 						|| (CFG_VAR_TYPE(var) == CFG_VAR_STR))
 					&& (*(char **)(group_inst->vars + var->offset) != NULL))
47daa2e1
 				num++;
 		}
 
 		replaced = (void **)shm_malloc(sizeof(void *) * (num + 2));
8e1d174c
 		if (!replaced) {
e3ecad34
 			SHM_MEM_ERROR;
8e1d174c
 			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)
08496ed4
 						&& ((CFG_VAR_TYPE(var) == CFG_VAR_STRING)
 							|| (CFG_VAR_TYPE(var) == CFG_VAR_STR))
 						&& (*(char **)(group_inst->vars + var->offset) != NULL)
 						) {
47daa2e1
 					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();
 
08496ed4
 	LM_INFO("group instance is deleted: %.*s[%u]\n",
 			group_name->len, group_name->s,
 			group_id);
8e1d174c
 
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);
 
08496ed4
 	LM_ERR("Failed to delete the group instance: %.*s[%u]\n",
 			group_name->len, group_name->s,
 			group_id);
8e1d174c
 
 	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
08496ed4
 	 * to make sure that a cfg driver has called the function
 	 * (very very weak security) */
fc444f6f
 	if (!ctx) {
08496ed4
 		LM_ERR("context is undefined\n");
fc444f6f
 		return 0;
 	}
 
 	if (!(group = cfg_lookup_group(group_name->s, group_name->len))) {
08496ed4
 		LM_ERR("group not found\n");
fc444f6f
 		return 0;
 	}
 
 	if (!cfg_shmized) {
 		/* group instances are stored in the additional variable list
 		 * before forking */
 		found = 0;
08496ed4
 		for (add_var = group->add_var;
 				add_var;
 				add_var = add_var->next
 			)
fc444f6f
 			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),
08496ed4
 					group->size,
 					group_id)
fc444f6f
 				!= 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,
08496ed4
 		unsigned int group_id, cfg_add_var_t **add_var_p)
92f85d59
 {
 	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,
08496ed4
 						* this additional variable only forces a new
 						* group instance to be created. */
92f85d59
 		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))) {
08496ed4
 			LM_ERR("Variable is not found: %.*s.%.*s\n",
 					group->name_len, group->name,
 					add_var->name_len, add_var->name);
92f85d59
 			goto error;
 		}
 
 		/* check whether the variable is read-only */
 		if (var->def->type & CFG_READONLY) {
08496ed4
 			LM_ERR("variable is read-only\n");
92f85d59
 			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
08496ed4
 				&& var->def->type & CFG_CB_ONLY_ONCE
 				) {
 			LM_ERR("This variable does not support muliple values.\n");
92f85d59
 			goto error;
 		}
 
 		switch(add_var->type) {
08496ed4
 			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:
 				LM_ERR("unsupported variable type: %d\n",
 						add_var->type);
 				goto error;
92f85d59
 		}
 		/* check whether we have to convert the type */
 		if (convert_val(add_var->type, val, CFG_INPUT_TYPE(var), &v))
 			goto error;
 
08496ed4
 		if ((CFG_INPUT_TYPE(var) == CFG_INPUT_INT)
 				&& (var->def->min || var->def->max)) {
92f85d59
 			/* perform a simple min-max check for integers */
 			if (((int)(long)v < var->def->min)
08496ed4
 					|| ((int)(long)v > var->def->max)) {
 				LM_ERR("integer value is out of range\n");
92f85d59
 				goto error;
 			}
 		}
 
 		if (var->def->on_change_cb) {
 			/* Call the fixup function.
08496ed4
 			 * The handle can point to the variables of the group instance. */
92f85d59
 			if (var->def->on_change_cb(ginst->vars,
08496ed4
 						&group_name,
 						&var_name,
 						&v) < 0) {
 				LM_ERR("fixup failed\n");
92f85d59
 				goto error;
 			}
 		}
 
 		p = ginst->vars + var->offset;
 		old_string = NULL;
 		/* set the new value */
 		switch (CFG_VAR_TYPE(var)) {
08496ed4
 			case CFG_VAR_INT:
 				*(int *)p = (int)(long)v;
 				break;
92f85d59
 
08496ed4
 			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;
92f85d59
 
08496ed4
 			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;
92f85d59
 
08496ed4
 			case CFG_VAR_POINTER:
 				*(void **)p = v;
 				break;
92f85d59
 
 		}
 		if (CFG_VAR_TEST_AND_SET(ginst, var) && old_string)
 			shm_free(old_string); /* the string was already in shm memory,
08496ed4
 								* it needs to be freed.
 								* This can happen when the same variable is set
 								* multiple times before forking. */
92f85d59
 
 		if (add_var->type == CFG_VAR_INT)
08496ed4
 			LM_INFO("%.*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);
92f85d59
 
 		else if (add_var->type == CFG_VAR_STRING)
08496ed4
 			LM_INFO("%.*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);
92f85d59
 
 		else /* str type */
08496ed4
 			LM_INFO("%.*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);
92f85d59
 
 		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:
08496ed4
 	LM_ERR("Failed to set the value for: %.*s[%u].%.*s\n",
 			group->name_len, group->name,
 			group_id,
 			add_var->name_len, add_var->name);
92f85d59
 	convert_val_cleanup();
 	return -1;
 }