瀏覽代碼

Merge remote branch 'origin/tirpi/cfg_framework_multivalue'

* origin/tirpi/cfg_framework_multivalue: (33 commits)
  cfg framework: fix uninitialized group_inst pointers
  xmlrpc: scan resets the error code
  cfg_rpc: group can be specified for cfg.list
  cfg_rpc: cfg.set and cfg.set_delayed commands added
  ctl: rpc->scan does not immediately send out errors
  cfg_rpc: documentation update - group instances
  cfg framework: multiple group instances is documented
  cfg framework: timer must reset the handles more frequently
  cfg framework: cfg_select() and cfg_reset() added
  cfg framework: group handle can be moved runtime
  cfg framework: group instance support in the script
  cfg framework: apply the values in the order they are set
  cfg framework: translate_pointer bugfix
  cfg framework: apply additional var list bugfix
  cfg framework: apply the additional variable list
  cfg framework: group instance support before forking
  cfg framework: CFG_GROUP_UNKNOWN group type
  cfg framework: cfg_commit() log message fixes
  cfg_db: updated to the cfg framework changes
  cfg_db: updated to the cfg framework changes
  ...
Miklos Tirpak 15 年之前
父節點
當前提交
b5cc26d622
共有 23 個文件被更改,包括 2254 次插入230 次删除
  1. 35 0
      action.c
  2. 6 0
      cfg.lex
  3. 26 1
      cfg.y
  4. 70 12
      cfg/cfg.c
  5. 19 0
      cfg/cfg.h
  6. 802 100
      cfg/cfg_ctx.c
  7. 44 14
      cfg/cfg_ctx.h
  8. 14 3
      cfg/cfg_script.c
  9. 1 0
      cfg/cfg_script.h
  10. 507 12
      cfg/cfg_struct.c
  11. 180 7
      cfg/cfg_struct.h
  12. 60 1
      doc/cfg.txt
  13. 3 3
      modules/cfg_db/cfg_db.c
  14. 43 17
      modules/cfg_rpc/README
  15. 205 13
      modules/cfg_rpc/cfg_rpc.c
  16. 58 16
      modules/cfg_rpc/doc/rpc.xml
  17. 99 25
      modules/ctl/binrpc_run.c
  18. 2 2
      modules/tls/tls_init.c
  19. 14 0
      modules/xmlrpc/xmlrpc.c
  20. 2 2
      modules_k/kex/mi_core.c
  21. 55 0
      route.c
  22. 5 2
      route_struct.h
  23. 4 0
      timer.c

+ 35 - 0
action.c

@@ -93,6 +93,7 @@
 #endif
 #include "switch.h"
 #include "events.h"
+#include "cfg/cfg_struct.h"
 
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -1492,6 +1493,40 @@ match_cleanup:
 			msg->rpl_send_flags.f|= SND_F_CON_CLOSE;
 			ret=1; /* continue processing */
 			break;
+		case CFG_SELECT_T:
+			if (a->val[0].type != CFG_GROUP_ST) {
+				BUG("unsupported parameter in CFG_SELECT_T: %d\n",
+						a->val[0].type);
+				ret=-1;
+				goto error;
+			}
+			switch(a->val[1].type) {
+				case NUMBER_ST:
+					v=(int)a->val[1].u.number;
+					break;
+				case RVE_ST:
+					if (rval_expr_eval_int(h, msg, &v, (struct rval_expr*)a->val[1].u.data) < 0) {
+						ret=-1;
+						goto error;
+					}
+					break;
+				default:
+					BUG("unsupported group id type in CFG_SELECT_T: %d\n",
+							a->val[1].type);
+					ret=-1;
+					goto error;
+			}
+			ret=(cfg_select((cfg_group_t*)a->val[0].u.data, v) == 0) ? 1 : -1;
+			break;
+		case CFG_RESET_T:
+			if (a->val[0].type != CFG_GROUP_ST) {
+				BUG("unsupported parameter in CFG_RESET_T: %d\n",
+						a->val[0].type);
+				ret=-1;
+				goto error;
+			}
+			ret=(cfg_reset((cfg_group_t*)a->val[0].u.data) == 0) ? 1 : -1;
+			break;
 /*
 		default:
 			LOG(L_CRIT, "BUG: do_action: unknown type %d\n", a->type);

+ 6 - 0
cfg.lex

@@ -251,6 +251,9 @@ WHILE			"while"
 
 INCLUDEFILE     "include_file"
 
+CFG_SELECT	"cfg_select"
+CFG_RESET	"cfg_reset"
+
 /*ACTION LVALUES*/
 URIHOST			"uri:host"
 URIPORT			"uri:port"
@@ -635,6 +638,9 @@ SUBST       subst
 
 <INITIAL>{INCLUDEFILE}  { count(); BEGIN(INCLF); }
 
+<INITIAL>{CFG_SELECT}	{ count(); yylval.strval=yytext; return CFG_SELECT; }
+<INITIAL>{CFG_RESET}	{ count(); yylval.strval=yytext; return CFG_RESET; }
+
 <INITIAL>{URIHOST}	{ count(); yylval.strval=yytext; return URIHOST; }
 <INITIAL>{URIPORT}	{ count(); yylval.strval=yytext; return URIPORT; }
 

+ 26 - 1
cfg.y

@@ -101,7 +101,7 @@
  * 2010-02-17  added blacklist imask (DST_BLST_*_IMASK) support (andrei)
 */
 
-%expect 5
+%expect 6
 
 %{
 
@@ -347,6 +347,8 @@ extern char *finame;
 %token CASE
 %token DEFAULT
 %token WHILE
+%token CFG_SELECT
+%token CFG_RESET
 %token URIHOST
 %token URIPORT
 %token MAX_LEN
@@ -1632,6 +1634,16 @@ cfg_var:
 	| cfg_var_id DOT cfg_var_id EQUAL error { 
 		yyerror("number or string expected"); 
 	}
+	| cfg_var_id LBRACK NUMBER RBRACK DOT cfg_var_id EQUAL NUMBER {
+		if (cfg_ginst_var_int($1, $3, $6, $8)) {
+			yyerror("variable cannot be added to the group instance");
+		}
+	}
+	| cfg_var_id LBRACK NUMBER RBRACK DOT cfg_var_id EQUAL STRING {
+		if (cfg_ginst_var_string($1, $3, $6, $8)) {
+			yyerror("variable cannot be added to the group instance");
+		}
+	}
 	;
 
 module_stm:
@@ -3261,6 +3273,19 @@ cmd:
 	| SET_RPL_CLOSE	{
 		$$=mk_action(SET_RPL_CLOSE_T, 0); set_cfg_pos($$);
 	}
+	| CFG_SELECT LPAREN STRING COMMA NUMBER RPAREN {
+		$$=mk_action(CFG_SELECT_T, 2, STRING_ST, $3, NUMBER_ST, (void*)$5); set_cfg_pos($$);
+	}
+	| CFG_SELECT LPAREN STRING COMMA rval_expr RPAREN {
+		$$=mk_action(CFG_SELECT_T, 2, STRING_ST, $3, RVE_ST, $5); set_cfg_pos($$);
+	}
+	| CFG_SELECT error { $$=0; yyerror("missing '(' or ')' ?"); }
+	| CFG_SELECT LPAREN error RPAREN { $$=0; yyerror("bad arguments, string and number expected"); }
+	| CFG_RESET LPAREN STRING RPAREN {
+		$$=mk_action(CFG_RESET_T, 1, STRING_ST, $3); set_cfg_pos($$);
+	}
+	| CFG_RESET error { $$=0; yyerror("missing '(' or ')' ?"); }
+	| CFG_RESET LPAREN error RPAREN { $$=0; yyerror("bad arguments, string expected"); }
 	| ID {mod_func_action = mk_action(MODULE0_T, 2, MODEXP_ST, NULL, NUMBER_ST,
 			0); } LPAREN func_params RPAREN	{
 		mod_func_action->val[0].u.data =

+ 70 - 12
cfg/cfg.c

@@ -43,6 +43,7 @@ int cfg_declare(char *group_name, cfg_def_t *def, void *values, int def_size,
 {
 	int	i, num, size, group_name_len;
 	cfg_mapping_t	*mapping = NULL;
+	cfg_group_t	*group;
 	int types;
 
 	/* check the number of the variables */
@@ -61,6 +62,7 @@ int cfg_declare(char *group_name, cfg_def_t *def, void *values, int def_size,
 	for (i=0, size=0; i<num; i++) {
 		mapping[i].def = &(def[i]);
 		mapping[i].name_len = strlen(def[i].name);
+		mapping[i].pos = i;
 		/* record all the types for sanity checks */
 		types|=1 << CFG_VAR_MASK(def[i].type);
 
@@ -141,18 +143,24 @@ int cfg_declare(char *group_name, cfg_def_t *def, void *values, int def_size,
 
 	group_name_len = strlen(group_name);
 	/* check for duplicates */
-	if (cfg_lookup_group(group_name, group_name_len)) {
-		LOG(L_ERR, "ERROR: register_cfg_def(): "
-			"configuration group has been already declared: %s\n",
-			group_name);
-		goto error;
+	if ((group = cfg_lookup_group(group_name, group_name_len))) {
+		if (group->dynamic != CFG_GROUP_UNKNOWN) {
+			/* conflict with another module/core group, or with a dynamic group */
+			LOG(L_ERR, "ERROR: register_cfg_def(): "
+				"configuration group has been already declared: %s\n",
+				group_name);
+			goto error;
+		}
+		/* An empty group is found which does not have any variable yet */
+		cfg_set_group(group, num, mapping, values, size, handle);
+	} else {
+		/* create a new group
+		I will allocate memory in shm mem for the variables later in a single block,
+		when we know the size of all the registered groups. */
+		if (!(group = cfg_new_group(group_name, group_name_len, num, mapping, values, size, handle)))
+			goto error;
 	}
-
-	/* create a new group
-	I will allocate memory in shm mem for the variables later in a single block,
-	when we know the size of all the registered groups. */
-	if (!cfg_new_group(group_name, group_name_len, num, mapping, values, size, handle))
-		goto error;
+	group->dynamic = CFG_GROUP_STATIC;
 
 	/* The cfg variables are ready to use, let us set the handle
 	before passing the new definitions to the drivers.
@@ -219,13 +227,63 @@ int cfg_declare_str(char *group_name, char *var_name, char *val, char *descr)
 	return 0;
 }
 
+/* Add a varibale to a group instance with integer type.
+ * The group instance is created if it does not exist.
+ * wrapper function for new_add_var()
+ */
+int cfg_ginst_var_int(char *group_name, unsigned int group_id, char *var_name,
+			int val)
+{
+	str	gname, vname;
+
+	gname.s = group_name;
+	gname.len = strlen(group_name);
+	vname.s = var_name;
+	vname.len = strlen(var_name);
+
+	return new_add_var(&gname, group_id, &vname,
+			(void *)(long)val, CFG_VAR_INT);
+}
+
+/* Add a varibale to a group instance with string type.
+ * The group instance is created if it does not exist.
+ * wrapper function for new_add_var()
+ */
+int cfg_ginst_var_string(char *group_name, unsigned int group_id, char *var_name,
+			char *val)
+{
+	str	gname, vname;
+
+	gname.s = group_name;
+	gname.len = strlen(group_name);
+	vname.s = var_name;
+	vname.len = strlen(var_name);
+
+	return new_add_var(&gname, group_id, &vname,
+			(void *)val, CFG_VAR_STRING);
+}
+
+/* Create a new group instance.
+ * wrapper function for new_add_var()
+ */
+int cfg_new_ginst(char *group_name, unsigned int group_id)
+{
+	str	gname;
+
+	gname.s = group_name;
+	gname.len = strlen(group_name);
+
+	return new_add_var(&gname, group_id, NULL /* var */,
+			NULL /* val */, 0 /* type */);
+}
+
 /* returns the handle of a cfg group */
 void **cfg_get_handle(char *gname)
 {
 	cfg_group_t	*group;
 
 	group = cfg_lookup_group(gname, strlen(gname));
-	if (!group || group->dynamic) return NULL;
+	if (!group || (group->dynamic != CFG_GROUP_STATIC)) return NULL;
 
 	return group->handle;
 }

+ 19 - 0
cfg/cfg.h

@@ -87,6 +87,25 @@ int cfg_declare_int(char *group_name, char *var_name,
 /* declares a single variable with str type */
 int cfg_declare_str(char *group_name, char *var_name, char *val, char *descr);
 
+/* Add a varibale to a group instance with integer type.
+ * The group instance is created if it does not exist.
+ * wrapper function for new_add_var()
+ */
+int cfg_ginst_var_int(char *group_name, unsigned int group_id, char *var_name,
+			int val);
+
+/* Add a varibale to a group instance with string type.
+ * The group instance is created if it does not exist.
+ * wrapper function for new_add_var()
+ */
+int cfg_ginst_var_string(char *group_name, unsigned int group_id, char *var_name,
+			char *val);
+
+/* Create a new group instance.
+ * wrapper function for new_add_var()
+ */
+int cfg_new_ginst(char *group_name, unsigned int group_id);
+
 /* returns the handle of a cfg group */
 void **cfg_get_handle(char *gname);
 

+ 802 - 100
cfg/cfg_ctx.c

@@ -77,7 +77,7 @@ int cfg_register_ctx(cfg_ctx_t **handle, cfg_on_declare on_declare_cb)
 		) {
 			/* dynamic groups are not ready, the callback
 			will be called later when the group is fixed-up */
-			if (group->dynamic) continue;
+			if (group->dynamic != CFG_GROUP_STATIC) continue;
 
 			gname.s = group->name;
 			gname.len = group->name_len;
@@ -239,6 +239,66 @@ error:
 		} \
 	} while(0)
 
+/* 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;
+
+	if (!(array = meta->array))
+		return 0;
+	for (i = 0; i < meta->num; i++) {
+		ginst = (cfg_group_inst_t *)((char *)array
+			+ (sizeof(cfg_group_inst_t) + group->size - 1) * i);
+
+		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;
+				ginst = (cfg_group_inst_t *)translate_pointer((char *)array,
+								(char *)meta->array,
+								(char *)ginst);
+				/* 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;
+}
+
 /* sets the value of a variable without the need of commit
  *
  * return value:
@@ -246,17 +306,20 @@ error:
  *  -1: error
  *   1: variable has not been found
  */
-int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
+int cfg_set_now(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			void *val, unsigned int val_type)
 {
+	int		i;
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*p, *v;
 	cfg_block_t	*block = NULL;
 	str		s, s2;
 	char		*old_string = NULL;
-	char		**replaced = NULL;
+	void		**replaced = NULL;
 	cfg_child_cb_t	*child_cb = NULL;
+	cfg_group_inst_t	*group_inst = NULL, *new_array = NULL;
+	unsigned char		*var_block;
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
@@ -266,6 +329,15 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		return -1;
 	}
 
+	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. */
+		return new_add_var(group_name, *group_id, var_name,
+				val, val_type);
+	}
+
 	/* look-up the group and the variable */
 	if (cfg_lookup_var(group_name, var_name, &group, &var))
 		return 1;
@@ -276,6 +348,19 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		goto error0;
 	}
 
+	/* 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;
+	}
+
 	/* check whether we have to convert the type */
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 		goto error0;
@@ -294,7 +379,25 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		/* Call the fixup function.
 		There is no need to set a temporary cfg handle,
 		becaue a single variable is changed */
-		if (var->def->on_change_cb(*(group->handle),
+		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) {
+				LOG(L_ERR, "ERROR: cfg_set_now(): local group instance %.*s[%u] is not found\n",
+					group_name->len, group_name->s, *group_id);
+				goto error0;
+			}
+			var_block = group_inst->vars;
+		}
+
+		if (var->def->on_change_cb(var_block,
 						group_name,
 						var_name,
 						&v) < 0) {
@@ -304,7 +407,10 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 
 	}
 
-	if (var->def->on_set_child_cb) {
+	/* 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) {
 		/* get the name of the variable from the internal struct,
 		because var_name may be freed before the callback needs it */
 		s.s = group->name;
@@ -325,16 +431,51 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		while the new one is prepared */
 		CFG_WRITER_LOCK();
 
+		if (group_id) {
+			group_inst = cfg_find_group(CFG_GROUP_META(*cfg_global, group),
+							group->size,
+							*group_id);
+			if (!group_inst) {
+				LOG(L_ERR, "ERROR: cfg_set_now(): global group instance %.*s[%u] is not found\n",
+					group_name->len, group_name->s, *group_id);
+				goto error;
+			}
+			var_block = group_inst->vars;
+		} else {
+			group_inst = NULL;
+			var_block = CFG_GROUP_DATA(*cfg_global, group);
+		}
+
 		if (var->def->type & CFG_ATOMIC) {
 			/* atomic change is allowed, we can rewrite the value
 			directly in the global config */
-			p = (*cfg_global)->vars+group->offset+var->offset;
+			p = var_block + var->offset;
 
 		} else {
 			/* clone the memory block, and prepare the modification */
 			if (!(block = cfg_clone_global())) goto error;
 
-			p = block->vars+group->offset+var->offset;
+			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;
+				group_inst = (cfg_group_inst_t *)translate_pointer((char *)new_array,
+					(char *)CFG_GROUP_META(*cfg_global, group)->array,
+					(char *)group_inst);
+				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;
 		}
 	} else {
 		/* we are allowed to rewrite the value on-the-fly
@@ -371,18 +512,43 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		break;
 
 	}
+	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 */
 
 	if (cfg_shmized) {
-		if (old_string) {
+		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),
+						group, var, p,
+						block ? 1 : 0) /* clone if needed */
+			)
+				goto error;
+			if (block && (CFG_GROUP_META(block, group)->array != CFG_GROUP_META(*cfg_global, group)->array))
+				new_array = CFG_GROUP_META(block, group)->array;
+		}
+
+		if (old_string || new_array) {
 			/* prepare the array of the replaced strings,
+			and replaced group instances,
 			they will be freed when the old block is freed */
-			replaced = (char **)shm_malloc(sizeof(char *)*2);
+			replaced = (void **)shm_malloc(sizeof(void *)
+					* ((old_string?1:0) + (new_array?1:0) + 1));
 			if (!replaced) {
 				LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
 				goto error;
 			}
-			replaced[0] = old_string;
-			replaced[1] = NULL;
+			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;
 		}
 		/* replace the global config with the new one */
 		if (block) cfg_install_global(block, replaced, child_cb, child_cb);
@@ -423,6 +589,9 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->s);
+	if (group_id)
+		LOG(L_INFO, "INFO: cfg_set_now(): group id = %u\n",
+			*group_id);
 
 	convert_val_cleanup();
 	return 0;
@@ -430,7 +599,9 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 error:
 	if (cfg_shmized) CFG_WRITER_UNLOCK();
 	if (block) cfg_block_free(block);
+	if (new_array) shm_free(new_array);
 	if (child_cb) cfg_child_cb_free(child_cb);
+	if (replaced) shm_free(replaced);
 
 error0:
 	LOG(L_ERR, "ERROR: cfg_set_now(): failed to set the variable: %.*s.%.*s\n",
@@ -443,45 +614,27 @@ error0:
 }
 
 /* wrapper function for cfg_set_now */
-int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
+int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			int val)
 {
-	return cfg_set_now(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
+	return cfg_set_now(ctx, group_name, group_id, var_name,
+				(void *)(long)val, CFG_VAR_INT);
 }
 
 /* wrapper function for cfg_set_now */
-int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
+int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			char *val)
 {
-	return cfg_set_now(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
+	return cfg_set_now(ctx, group_name, group_id, var_name,
+				(void *)val, CFG_VAR_STRING);
 }
 
 /* wrapper function for cfg_set_now */
-int cfg_set_now_str(cfg_ctx_t *ctx, str *group_name, str *var_name, str *val)
+int cfg_set_now_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			str *val)
 {
-	return cfg_set_now(ctx, group_name, var_name, (void *)val, CFG_VAR_STR);
-}
-
-/* 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;
-	}
+	return cfg_set_now(ctx, group_name, group_id, var_name,
+				(void *)val, CFG_VAR_STR);
 }
 
 /* sets the value of a variable but does not commit the change
@@ -491,22 +644,24 @@ static int cfg_var_size(cfg_mapping_t *var)
  *  -1: error
  *   1: variable has not been found
  */
-int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
+int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			void *val, unsigned int val_type)
 {
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	void		*v;
-	char		*temp_handle;
+	unsigned char	*temp_handle;
 	int		temp_handle_created;
-	cfg_changed_var_t	*changed = NULL;
+	cfg_changed_var_t	*changed = NULL, **changed_p;
 	int		size;
 	str		s;
+	cfg_group_inst_t	*group_inst = NULL;
+	unsigned char		*var_block;
 
 	if (!cfg_shmized)
 		/* the cfg has not been shmized yet, there is no
 		point in registering the change and committing it later */
-		return cfg_set_now(ctx, group_name, var_name,
+		return cfg_set_now(ctx, group_name, group_id, var_name,
 					val, val_type);
 
 	if (!ctx) {
@@ -524,6 +679,19 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		goto error0;
 	}
 
+	/* 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_delayed(): This variable does not support muliple values.\n");
+		goto error0;
+	}
+
 	/* check whether we have to convert the type */
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 		goto error0;
@@ -549,31 +717,56 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		Only the values within the group are applied,
 		other modifications are not visible to the callback.
 		The local config is the base. */
+		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;
+		}
 
 		if (ctx->changed_first) {
-			temp_handle = (char *)pkg_malloc(group->size);
+			temp_handle = (unsigned char *)pkg_malloc(group->size);
 			if (!temp_handle) {
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): "
 					"not enough memory\n");
 				goto error;
 			}
 			temp_handle_created = 1;
-			memcpy(temp_handle, *(group->handle), group->size);
+			memcpy(temp_handle, var_block, group->size);
 
 			/* apply the changes */
 			for (	changed = ctx->changed_first;
 				changed;
 				changed = changed->next
 			) {
-				if (changed->group != group) continue;
-
-				memcpy(	temp_handle + changed->var->offset,
-					changed->new_val.vraw,
-					cfg_var_size(changed->var));
+				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));
 			}
 		} else {
 			/* there is not any change */
-			temp_handle = *(group->handle);
+			temp_handle = var_block;
 			temp_handle_created = 0;
 		}
 			
@@ -600,6 +793,10 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 	memset(changed, 0, size);
 	changed->group = group;
 	changed->var = var;
+	if (group_id) {
+		changed->group_id = *group_id;
+		changed->group_id_set = 1;
+	}
 
 	switch (CFG_VAR_TYPE(var)) {
 
@@ -628,15 +825,27 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 
 	}
 
-	/* Add the new item to the end of the linked list,
-	The commit will go though the list from the first item,
-	so the list is kept in order */
-	if (ctx->changed_first)
-		ctx->changed_last->next = changed;
-	else
-		ctx->changed_first = changed;
-
-	ctx->changed_last = changed;
+	/* 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;
 
 	CFG_CTX_UNLOCK(ctx);
 
@@ -666,6 +875,11 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->s,
 			ctx);
+	if (group_id)
+		LOG(L_INFO, "INFO: cfg_set_delayed(): group id = %u "
+			"[context=%p]\n",
+			*group_id,
+			ctx);
 
 	convert_val_cleanup();
 	return 0;
@@ -683,21 +897,27 @@ error0:
 }
 
 /* wrapper function for cfg_set_delayed */
-int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
+int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+				int val)
 {
-	return cfg_set_delayed(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
+	return cfg_set_delayed(ctx, group_name, group_id, var_name,
+				(void *)(long)val, CFG_VAR_INT);
 }
 
 /* wrapper function for cfg_set_delayed */
-int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
+int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+				char *val)
 {
-	return cfg_set_delayed(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
+	return cfg_set_delayed(ctx, group_name, group_id, var_name,
+				(void *)val, CFG_VAR_STRING);
 }
 
 /* wrapper function for cfg_set_delayed */
-int cfg_set_delayed_str(cfg_ctx_t *ctx, str *group_name, str *var_name, str *val)
+int cfg_set_delayed_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+				str *val)
 {
-	return cfg_set_delayed(ctx, group_name, var_name, (void *)val, CFG_VAR_STR);
+	return cfg_set_delayed(ctx, group_name, group_id, var_name,
+				(void *)val, CFG_VAR_STR);
 }
 
 /* commits the previously prepared changes within the context */
@@ -705,14 +925,16 @@ int cfg_commit(cfg_ctx_t *ctx)
 {
 	int	replaced_num = 0;
 	cfg_changed_var_t	*changed, *changed2;
-	cfg_block_t	*block;
-	char	**replaced = NULL;
+	cfg_block_t	*block = NULL;
+	void	**replaced = NULL;
 	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;
 	str	s, s2;
+	cfg_group_t	*group;
+	cfg_group_inst_t	*group_inst = NULL;
 
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_commit(): context is undefined\n");
@@ -728,19 +950,30 @@ int cfg_commit(cfg_ctx_t *ctx)
 	/* is there any change? */
 	if (!ctx->changed_first) goto done;
 
-	/* count the number of replaced strings,
-	and prepare the linked list of per-child process
-	callbacks, that will be added to the global list */
-	for (	changed = ctx->changed_first;
+	/* 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;
 		changed;
 		changed = changed->next
 	) {
+		/* 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. */
 		if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 		|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 			replaced_num++;
 
+		/* See the above comments for strings */
+		if (group != changed->group) {
+			replaced_num++;
+			group = changed->group;
+		}
 
-		if (changed->var->def->on_set_child_cb) {
+		if (!changed->group_id_set && changed->var->def->on_set_child_cb) {
 			s.s = changed->group->name;
 			s.len = changed->group->name_len;
 			s2.s = changed->var->def->name;
@@ -760,11 +993,11 @@ int cfg_commit(cfg_ctx_t *ctx)
 
 	if (replaced_num) {
 		/* allocate memory for the replaced string array */
-		size = sizeof(char *)*(replaced_num + 1);
-		replaced = (char **)shm_malloc(size);
+		size = sizeof(void *)*(replaced_num + 1);
+		replaced = (void **)shm_malloc(size);
 		if (!replaced) {
 			LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
-			goto error;
+			goto error0;
 		}
 		memset(replaced, 0 , size);
 	}
@@ -774,23 +1007,57 @@ int cfg_commit(cfg_ctx_t *ctx)
 	CFG_WRITER_LOCK();
 
 	/* clone the memory block, and prepare the modification */
-	if (!(block = cfg_clone_global())) {
-		CFG_WRITER_UNLOCK();
+	if (!(block = cfg_clone_global()))
 		goto error;
-	}
 
-	/* apply the modifications to the buffer */
+	/* 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. */
 	replaced_num = 0;
-	for (	changed = ctx->changed_first;
+	for (	changed = ctx->changed_first, group = NULL; /* group points to the
+							last group array that has been cloned */
 		changed;
 		changed = changed->next
 	) {
-		p = block->vars
-			+ changed->group->offset
-			+ changed->var->offset;
+		if (!changed->group_id_set) {
+			p = CFG_GROUP_DATA(block, changed->group)
+				+ changed->var->offset;
+			group_inst = NULL; /* force the look-up of the next group_inst */
+		} 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))
+				) {
+					LOG(L_ERR, "ERROR: cfg_commit(): group array cannot be cloned for %.*s[%u]\n",
+						group->name_len, group->name, changed->group_id);
+					goto error;
+				}
+
+				replaced[replaced_num] = CFG_GROUP_META(*cfg_global, group)->array;
+				replaced_num++;
 
-		if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
-		|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) {
+				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) {
+				LOG(L_ERR, "ERROR: cfg_commit(): global group instance %.*s[%u] is not found\n",
+					group->name_len, group->name, changed->group_id);
+				goto error;
+			}
+			p = group_inst->vars + changed->var->offset;
+		}
+
+		if (((changed->group_id_set && CFG_VAR_TEST_AND_SET(group_inst, changed->var))
+			|| !changed->group_id_set)
+		&& ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
+			|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) 
+		) {
 			replaced[replaced_num] = *(char **)p;
 			if (replaced[replaced_num])
 				replaced_num++;
@@ -802,13 +1069,33 @@ int cfg_commit(cfg_ctx_t *ctx)
 		memcpy(	p,
 			changed->new_val.vraw,
 			cfg_var_size(changed->var));
+
+		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++;
+			}
+		}
 	}
 
 	/* replace the global config with the new one */
 	cfg_install_global(block, replaced, child_cb_first, child_cb_last);
 	CFG_WRITER_UNLOCK();
 
-	/* free the changed list */	
+	/* free the changed list */
 	for (	changed = ctx->changed_first;
 		changed;
 		changed = changed2
@@ -817,7 +1104,6 @@ int cfg_commit(cfg_ctx_t *ctx)
 		shm_free(changed);
 	}
 	ctx->changed_first = NULL;
-	ctx->changed_last = NULL;
 
 done:
 	LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
@@ -828,9 +1114,24 @@ done:
 	return 0;
 
 error:
-	CFG_CTX_UNLOCK(ctx);
+	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);
 
 error0:
+	CFG_CTX_UNLOCK(ctx);
 
 	if (child_cb_first) cfg_child_cb_free(child_cb_first);
 	if (replaced) shm_free(replaced);
@@ -872,7 +1173,6 @@ int cfg_rollback(cfg_ctx_t *ctx)
 		shm_free(changed);
 	}
 	ctx->changed_first = NULL;
-	ctx->changed_last = NULL;
 
 	CFG_CTX_UNLOCK(ctx);
 
@@ -885,7 +1185,7 @@ int cfg_rollback(cfg_ctx_t *ctx)
  * -1 - error
  *  1 - variable exists, but it is not readable
  */
-int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
+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)
 {
 	cfg_group_t	*group;
@@ -893,6 +1193,7 @@ int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
 	void		*p;
 	static str	s;	/* we need the value even
 				after the function returns */
+	cfg_group_inst_t	*group_inst;
 
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
@@ -913,10 +1214,27 @@ int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		return 1;
 	}
 
-	/* 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;
+	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;
+	}
 
 	switch (CFG_VAR_TYPE(var)) {
 	case CFG_VAR_INT:
@@ -1005,13 +1323,18 @@ int cfg_diff_init(cfg_ctx_t *ctx,
 
 /* return the pending changes that have not been
  * committed yet
+ * return value:
+ *	1: valid value is found
+ *	0: no more changed value found
+ *	-1: error occured 
  */
 int cfg_diff_next(void **h,
-			str *gname, str *vname,
+			str *gname, unsigned int **gid, str *vname,
 			void **old_val, void **new_val,
 			unsigned int *val_type)
 {
 	cfg_changed_var_t	*changed;
+	cfg_group_inst_t	*group_inst;
 	union cfg_var_value* pval;
 	static str	old_s, new_s;	/* we need the value even
 					after the function returns */
@@ -1021,14 +1344,32 @@ int cfg_diff_next(void **h,
 
 	gname->s = changed->group->name;
 	gname->len = changed->group->name_len;
+	*gid = (changed->group_id_set ? &changed->group_id : NULL);
 	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 */
-	pval = (union cfg_var_value*)
-			(*(changed->group->handle) + changed->var->offset);
+	if (!changed->group_id_set) {
+		pval = (union cfg_var_value*)
+				(*(changed->group->handle) + changed->var->offset);
+	} else {
+		if (!cfg_local) {
+			LOG(L_ERR, "ERROR: cfg_diff_next(): Local configuration is missing\n");
+			return -1;
+		}
+		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);
+			return -1;
+		}
+		pval = (union cfg_var_value*)
+				(group_inst->vars + changed->var->offset);
+	}
 
 	switch (CFG_VAR_TYPE(changed->var)) {
 	case CFG_VAR_INT:
@@ -1070,3 +1411,364 @@ void cfg_diff_release(cfg_ctx_t *ctx)
 
 	CFG_CTX_UNLOCK(ctx);
 }
+
+/* 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) {
+		/* 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 */);
+	}
+
+	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),
+		group->size);
+
+	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 */
+		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);
+
+	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;
+}
+
+/* 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;
+
+	/* 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 */
+		replaced = (void **)shm_malloc(sizeof(void *) * 2);
+		if (!replaced) {
+			LOG(L_ERR, "ERROR: cfg_del_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_del_group_inst(): "
+		"group instance is deleted: %.*s[%u]\n",
+		group_name->len, group_name->s,
+		group_id);
+
+	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;
+}
+
+/* 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;
+}

+ 44 - 14
cfg/cfg_ctx.h

@@ -49,6 +49,9 @@ typedef struct _cfg_changed_var {
 	cfg_mapping_t	*var;
 	struct _cfg_changed_var	*next;
 
+	unsigned int	group_id; /* valid only if group_id_set==1 */
+	unsigned char	group_id_set;
+
 	/* blob that contains the new value */
 	union cfg_var_value new_val; /* variable size */
 } cfg_changed_var_t;
@@ -61,7 +64,6 @@ typedef struct _cfg_ctx {
 	/* variables that are already changed
 	but have not been committed yet */
 	cfg_changed_var_t	*changed_first;
-	cfg_changed_var_t	*changed_last;
 	/* lock protecting the linked-list of
 	changed variables */
 	gen_lock_t		lock;
@@ -84,18 +86,24 @@ int cfg_register_ctx(cfg_ctx_t **handle, cfg_on_declare on_declare_cb);
 void cfg_ctx_destroy(void);
 
 /*! \brief set the value of a variable without the need of explicit commit */
-int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
+int cfg_set_now(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			void *val, unsigned int val_type);
-int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val);
-int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val);
-int cfg_set_now_str(cfg_ctx_t *ctx, str *group_name, str *var_name, str *val);
+int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			int val);
+int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			char *val);
+int cfg_set_now_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			str *val);
 
 /* sets the value of a variable but does not commit the change */
-int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
+int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
 			void *val, unsigned int val_type);
-int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val);
-int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val);
-int cfg_set_delayed_str(cfg_ctx_t *ctx, str *group_name, str *var_name, str *val);
+int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			int val);
+int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			char *val);
+int cfg_set_delayed_str(cfg_ctx_t *ctx, str *group_name, unsigned int *group_id, str *var_name,
+			str *val);
 
 /*! \brief commits the previously prepared changes within the context */
 int cfg_commit(cfg_ctx_t *ctx);
@@ -104,7 +112,7 @@ int cfg_commit(cfg_ctx_t *ctx);
 int cfg_rollback(cfg_ctx_t *ctx);
 
 /*! \brief returns the value of a variable */
-int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
+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);
 
 /*! \brief returns the description of a variable */
@@ -149,26 +157,48 @@ int cfg_diff_init(cfg_ctx_t *ctx,
 
 /*! \brief return the pending changes that have not been
  * committed yet
+ * return value:
+ *	1: valid value is found
+ *	0: no more changed value found
+ *	-1: error occured
+ *
  *
  * can be used as follows:
  *
  * void *handle;
  * if (cfg_diff_init(ctx, &handle)) return -1
- * while (cfg_diff_next(&handle
- *			&group_name, &var_name,
+ * while ((err = cfg_diff_next(&handle
+ *			&group_name, &group_id, &var_name,
  *			&old_val, &new_val
- *			&val_type)
+ *			&val_type)) > 0
  * ) {
  *		...
  * }
  * cfg_diff_release(ctx);
+ * if (err) {
+ *	error occured, the changes cannot be retrieved
+ *	...
+ * }
  */
 int cfg_diff_next(void **h,
-			str *gname, str *vname,
+			str *gname, unsigned int **gid, str *vname,
 			void **old_val, void **new_val,
 			unsigned int *val_type);
 
 /*! \brief destroy the handle of cfg_diff_next() */
 void cfg_diff_release(cfg_ctx_t *ctx);
 
+/* Add a new instance to an existing group */
+int cfg_add_group_inst(cfg_ctx_t *ctx, str *group_name, unsigned int group_id);
+
+/* Delete an instance of a group */
+int cfg_del_group_inst(cfg_ctx_t *ctx, str *group_name, unsigned int group_id);
+
+/* 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.
+ */
+int cfg_apply_list(cfg_group_inst_t *ginst, cfg_group_t *group,
+			unsigned int group_id, cfg_add_var_t **add_var_p);
+
 #endif /* _CFG_CTX_H */

+ 14 - 3
cfg/cfg_script.c

@@ -56,14 +56,14 @@ cfg_script_var_t *new_cfg_script_var(char *gname, char *vname, unsigned int type
 	/* the group may have been already declared */
 	group = cfg_lookup_group(gname, gname_len);
 	if (group) {
-		if (group->dynamic == 0) {
+		if (group->dynamic == CFG_GROUP_STATIC) {
 			/* the group has been already declared by a module or by the core */
 			LOG(L_ERR, "ERROR: new_cfg_script_var(): "
 				"configuration group has been already declared: %s\n",
 				gname);
 			return NULL;
 		}
-		/* the dynamic group is found */
+		/* the dynamic or empty group is found */
 		/* verify that the variable does not exist */
 		for (	var = (cfg_script_var_t *)group->vars;
 			var;
@@ -76,6 +76,8 @@ cfg_script_var_t *new_cfg_script_var(char *gname, char *vname, unsigned int type
 				return NULL;
 			}
 		}
+		if (group->dynamic == CFG_GROUP_UNKNOWN)
+			group->dynamic = CFG_GROUP_DYNAMIC;
 
 	} else {
 		/* create a new group with NULL values, we will fix it later,
@@ -85,7 +87,7 @@ cfg_script_var_t *new_cfg_script_var(char *gname, char *vname, unsigned int type
 					NULL /* vars */, 0 /* size */, NULL /* handle */);
 					
 		if (!group) goto error;
-		group->dynamic = 1;
+		group->dynamic = CFG_GROUP_DYNAMIC;
 	}
 
 	switch (type) {
@@ -103,7 +105,15 @@ cfg_script_var_t *new_cfg_script_var(char *gname, char *vname, unsigned int type
 		LOG(L_ERR, "ERROR: new_cfg_script_var(): unsupported variable type\n");
 		return NULL;
 	}
+
 	group->num++;
+	if (group->num > CFG_MAX_VAR_NUM) {
+		LOG(L_ERR, "ERROR: new_cfg_script_var(): too many variables (%d) within a single group,"
+			" the limit is %d. Increase CFG_MAX_VAR_NUM, or split the group into multiple"
+			" definitions.\n",
+			group->num, CFG_MAX_VAR_NUM);
+		return NULL;
+	}
 
 	var = (cfg_script_var_t *)pkg_malloc(sizeof(cfg_script_var_t));
 	if (!var) goto error;
@@ -173,6 +183,7 @@ int cfg_script_fixup(cfg_group_t *group, unsigned char *block)
 
 		mapping[i].def = &(def[i]);
 		mapping[i].name_len = script_var->name_len;
+		mapping[i].pos = i;
 
 		switch (script_var->type) {
 		case CFG_VAR_INT:

+ 1 - 0
cfg/cfg_script.h

@@ -29,6 +29,7 @@
 #define _CFG_SCRIPT_H
 
 #include "../str.h"
+#include "cfg_struct.h"
 
 /* structure used for temporary storing the variables
  * which are declared in the script */

+ 507 - 12
cfg/cfg_struct.c

@@ -32,6 +32,7 @@
 #include "../mem/shm_mem.h"
 #include "../ut.h"
 #include "../locking.h"
+#include "../bit_scan.h"
 #include "cfg_ctx.h"
 #include "cfg_script.h"
 #include "cfg_select.h"
@@ -42,7 +43,7 @@ cfg_block_t	**cfg_global = NULL;	/* pointer to the active cfg block */
 cfg_block_t	*cfg_local = NULL;	/* per-process pointer to the active cfg block.
 					Updated only when the child process
 					finishes working on the SIP message */
-static int	cfg_block_size = 0;	/* size of the cfg block (constant) */
+int		cfg_block_size = 0;	/* size of the cfg block including the meta-data (constant) */
 gen_lock_t	*cfg_global_lock = 0;	/* protects *cfg_global */
 gen_lock_t	*cfg_writer_lock = 0;	/* This lock makes sure that two processes do not
 					try to clone *cfg_global at the same time.
@@ -55,6 +56,12 @@ cfg_child_cb_t	**cfg_child_cb_first = NULL;	/* first item of the per-child proce
 						callback list */
 cfg_child_cb_t	**cfg_child_cb_last = NULL;	/* last item of the above list */
 cfg_child_cb_t	*cfg_child_cb = NULL;	/* pointer to the previously executed cb */	
+int		cfg_ginst_count = 0;	/* number of group instances set within the child process */
+
+
+/* forward declarations */
+static void del_add_var_list(cfg_group_t *group);
+static int apply_add_var_list(cfg_block_t *block, cfg_group_t *group);
 
 /* creates a new cfg group, and adds it to the linked list */
 cfg_group_t *cfg_new_group(char *name, int name_len,
@@ -68,6 +75,14 @@ cfg_group_t *cfg_new_group(char *name, int name_len,
 		return NULL;
 	}
 
+	if (num > CFG_MAX_VAR_NUM) {
+		LOG(L_ERR, "ERROR: cfg_new_group(): too many variables (%d) within a single group,"
+			" the limit is %d. Increase CFG_MAX_VAR_NUM, or split the group into multiple"
+			" definitions.\n",
+			num, CFG_MAX_VAR_NUM);
+		return NULL;
+	}
+
 	group = (cfg_group_t *)pkg_malloc(sizeof(cfg_group_t)+name_len-1);
 	if (!group) {
 		LOG(L_ERR, "ERROR: cfg_new_group(): not enough memory\n");
@@ -90,6 +105,18 @@ cfg_group_t *cfg_new_group(char *name, int name_len,
 	return group;
 }
 
+/* Set the values of an existing cfg group. */
+void cfg_set_group(cfg_group_t *group,
+		int num, cfg_mapping_t *mapping,
+		char *vars, int size, void **handle)
+{
+	group->num = num;
+	group->mapping = mapping;
+	group->vars = vars;
+	group->size = size;
+	group->handle = handle;
+}
+
 /* clones a string to shared memory
  * (src and dst can be the same)
  */
@@ -162,19 +189,37 @@ int cfg_shmize(void)
 	if (!cfg_group) return 0;
 
 	/* Let us allocate one memory block that
-	will contain all the variables */
+	 * will contain all the variables + meta-data
+	 * in the following form:
+	 * |-----------|
+	 * | meta-data | <- group A: meta_offset
+	 * | variables | <- group A: var_offset
+	 * |-----------|
+	 * | meta-data | <- group B: meta_offset
+	 * | variables | <- group B: var_offset
+	 * |-----------|
+	 * |    ...    |
+	 * |-----------|
+	 *
+	 * The additional array for the multiple values
+	 * of the same variable is linked to the meta-data.
+	 */
 	for (	size=0, group = cfg_group;
 		group;
 		group=group->next
 	) {
 		size = ROUND_POINTER(size);
-		group->offset = size;
+		group->meta_offset = size;
+		size += sizeof(cfg_group_meta_t);
+
+		size = ROUND_POINTER(size);
+		group->var_offset = size;
 		size += group->size;
 	}
 
 	block = (cfg_block_t*)shm_malloc(sizeof(cfg_block_t)+size-1);
 	if (!block) {
-		LOG(L_ERR, "ERROR: cfg_clone_str(): not enough shm memory\n");
+		LOG(L_ERR, "ERROR: cfg_shmize(): not enough shm memory\n");
 		goto error;
 	}
 	memset(block, 0, sizeof(cfg_block_t)+size-1);
@@ -185,29 +230,40 @@ int cfg_shmize(void)
 		group;
 		group=group->next
 	) {
-		if (group->dynamic == 0) {
+		if (group->dynamic == CFG_GROUP_STATIC) {
 			/* clone the strings to shm mem */
 			if (cfg_shmize_strings(group)) goto error;
 
 			/* copy the values to the new block */
-			memcpy(block->vars+group->offset, group->vars, group->size);
-		} else {
+			memcpy(CFG_GROUP_DATA(block, group), group->vars, group->size);
+		} else if (group->dynamic == CFG_GROUP_DYNAMIC) {
 			/* The group was declared with NULL values,
 			 * we have to fix it up.
 			 * The fixup function takes care about the values,
 			 * it fills up the block */
-			if (cfg_script_fixup(group, block->vars+group->offset)) goto error;
+			if (cfg_script_fixup(group, CFG_GROUP_DATA(block, group))) goto error;
 
 			/* Notify the drivers about the new config definition.
 			 * Temporary set the group handle so that the drivers have a chance to
 			 * overwrite the default values. The handle must be reset after this
 			 * because the main process does not have a local configuration. */
-			*(group->handle) = block->vars+group->offset;
+			*(group->handle) = CFG_GROUP_DATA(block, group);
 			cfg_notify_drivers(group->name, group->name_len,
 					group->mapping->def);
 			*(group->handle) = NULL;
+		} else {
+			LOG(L_ERR, "ERROR: cfg_shmize(): Configuration group is declared "
+					"without any variable: %.*s\n",
+					group->name_len, group->name);
+			goto error;
 		}
+
+		/* Create the additional group instances with applying
+		the temporary list. */
+		if (apply_add_var_list(block, group))
+			goto error;
 	}
+
 	/* try to fixup the selects that failed to be fixed-up previously */
 	if (cfg_fixup_selects()) goto error;
 
@@ -243,11 +299,11 @@ static void cfg_destory_groups(unsigned char *block)
 				(CFG_VAR_TYPE(&mapping[i]) == CFG_VAR_STR)) &&
 					mapping[i].flag & cfg_var_shmized) {
 
-						old_string = *(char **)(block + group->offset + mapping[i].offset);
+						old_string = *(char **)(block + group->var_offset + mapping[i].offset);
 						if (old_string) shm_free(old_string);
 				}
 
-		if (group->dynamic) {
+		if (group->dynamic == CFG_GROUP_DYNAMIC) {
 			/* the group was dynamically allocated */
 			cfg_script_destroy(group);
 		} else {
@@ -255,6 +311,8 @@ static void cfg_destory_groups(unsigned char *block)
 			pointers are just set to static variables */
 			if (mapping) pkg_free(mapping);
 		}
+		/* Delete the additional variable list */
+		del_add_var_list(group);
 
 		group2 = group->next;
 		pkg_free(group);
@@ -538,6 +596,29 @@ int cfg_lookup_var(str *gname, str *vname,
 	return -1;
 }
 
+/* searches a variable definition within a group by its name */
+cfg_mapping_t *cfg_lookup_var2(cfg_group_t *group, char *name, int len)
+{
+	int	i;
+
+	if (!group->mapping) return NULL; /* dynamic group is not ready */
+
+	for (	i = 0;
+		i < group->num;
+		i++
+	) {
+		if ((group->mapping[i].name_len == len)
+		&& (memcmp(group->mapping[i].def->name, name, len)==0)) {
+			return &(group->mapping[i]);
+		}
+	}
+
+	LOG(L_DBG, "DEBUG: cfg_lookup_var2(): variable not found: %.*s.%.*s\n",
+			group->name_len, group->name,
+			len, name);
+	return NULL;
+}
+
 /* clones the global config block
  * WARNING: unsafe, cfg_writer_lock or cfg_global_lock must be held!
  */
@@ -558,6 +639,130 @@ cfg_block_t *cfg_clone_global(void)
 	return block;
 }
 
+/* Clone an array of configuration group instances. */
+cfg_group_inst_t *cfg_clone_array(cfg_group_meta_t *meta, cfg_group_t *group)
+{
+	cfg_group_inst_t	*new_array;
+	int			size;
+
+	if (!meta->array || !meta->num)
+		return NULL;
+
+	size = (sizeof(cfg_group_inst_t) + group->size - 1) * meta->num;
+	new_array = (cfg_group_inst_t *)shm_malloc(size);
+	if (!new_array) {
+		LOG(L_ERR, "ERROR: cfg_clone_array(): not enough shm memory\n");
+		return NULL;
+	}
+	memcpy(new_array, meta->array, size);
+
+	return new_array;
+}
+
+/* Extend the array of configuration group instances with one more instance.
+ * Only the ID of the new group is set, nothing else. */
+cfg_group_inst_t *cfg_extend_array(cfg_group_meta_t *meta, cfg_group_t *group,
+				unsigned int group_id,
+				cfg_group_inst_t **new_group)
+{
+	int			i;
+	cfg_group_inst_t	*new_array, *old_array;
+	int			inst_size;
+
+	inst_size = sizeof(cfg_group_inst_t) + group->size - 1;
+	new_array = (cfg_group_inst_t *)shm_malloc(inst_size * (meta->num + 1));
+	if (!new_array) {
+		LOG(L_ERR, "ERROR: cfg_extend_array(): not enough shm memory\n");
+		return NULL;
+	}
+	/* Find the position of the new group in the array. The array is ordered
+	by the group IDs. */
+	old_array = meta->array;
+	for (	i = 0;
+		(i < meta->num)
+			&& (((cfg_group_inst_t *)((char *)old_array + inst_size * i))->id < group_id);
+		i++
+	);
+	if (i > 0)
+		memcpy(	new_array,
+			old_array,
+			inst_size * i);
+
+	memset((char*)new_array + inst_size * i, 0, inst_size);
+	*new_group = (cfg_group_inst_t *)((char*)new_array + inst_size * i);
+	(*new_group)->id = group_id;
+
+	if (i < meta->num)
+		memcpy(	(char*)new_array + inst_size * (i + 1),
+			(char*)old_array + inst_size * i,
+			inst_size * (meta->num - i));
+
+	return new_array;
+}
+
+/* Remove an instance from a group array.
+ * inst must point to an instance within meta->array.
+ * *_new_array is set to the newly allocated array. */
+int cfg_collapse_array(cfg_group_meta_t *meta, cfg_group_t *group,
+				cfg_group_inst_t *inst,
+				cfg_group_inst_t **_new_array)
+{
+	cfg_group_inst_t	*new_array, *old_array;
+	int			inst_size, offset;
+
+	if (!meta->num)
+		return -1;
+
+	if (meta->num == 1) {
+		*_new_array = NULL;
+		return 0;
+	}
+
+	inst_size = sizeof(cfg_group_inst_t) + group->size - 1;
+	new_array = (cfg_group_inst_t *)shm_malloc(inst_size * (meta->num - 1));
+	if (!new_array) {
+		LOG(L_ERR, "ERROR: cfg_collapse_array(): not enough shm memory\n");
+		return -1;
+	}
+
+	old_array = meta->array;
+	offset = (char *)inst - (char *)old_array;
+	if (offset)
+		memcpy(	new_array,
+			old_array,
+			offset);
+
+	if (meta->num * inst_size > offset + inst_size)
+		memcpy( (char *)new_array + offset,
+			(char *)old_array + offset + inst_size,
+			(meta->num - 1) * inst_size - offset);
+
+	*_new_array = new_array;
+	return 0;
+}
+
+/* Find the group instance within the meta-data based on the group_id */
+cfg_group_inst_t *cfg_find_group(cfg_group_meta_t *meta, int group_size, unsigned int group_id)
+{
+	int	i;
+	cfg_group_inst_t *ginst;
+
+	if (!meta)
+		return NULL;
+
+	/* For now, search lineray.
+	TODO: improve */
+	for (i = 0; i < meta->num; i++) {
+		ginst = (cfg_group_inst_t *)((char *)meta->array
+			+ (sizeof(cfg_group_inst_t) + group_size - 1) * i);
+		if (ginst->id == group_id)
+			return ginst;
+		else if (ginst->id > group_id)
+			break; /* needless to continue, the array is ordered */
+	}
+	return NULL;
+}
+
 /* append new callbacks to the end of the child callback list
  *
  * WARNING: the function is unsafe, either hold CFG_LOCK(),
@@ -577,7 +782,7 @@ void cfg_install_child_cb(cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last)
  * cb_first and cb_last define a linked list of per-child process
  * callbacks. This list is added to the global linked list.
  */
-void cfg_install_global(cfg_block_t *block, char **replaced,
+void cfg_install_global(cfg_block_t *block, void **replaced,
 			cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last)
 {
 	cfg_block_t* old_cfg;
@@ -657,3 +862,293 @@ void cfg_child_cb_free(cfg_child_cb_t *child_cb_first)
 		shm_free(cb);
 	}
 }
+
+/* Allocate memory for a new additional variable
+ * and link it to a configuration group.
+ * type==0 results in creating a new group instance with the default values.
+ * The group is created with CFG_GROUP_UNKNOWN type if it does not exist.
+ * Note: this function is usable only before the configuration is shmized.
+ */
+int new_add_var(str *group_name, unsigned int group_id, str *var_name,
+				void *val, unsigned int type)
+{
+	cfg_group_t	*group;
+	cfg_add_var_t	*add_var = NULL, **add_var_p;
+	int		len;
+
+	LOG(L_DBG, "DEBUG: new_add_var(): declaring a new variable instance %.*s[%u].%.*s\n",
+			group_name->len, group_name->s,
+			group_id,
+			var_name->len, var_name->s);
+
+	if (cfg_shmized) {
+		LOG(L_ERR, "ERROR: new_add_var(): too late, the configuration has already been shmized\n");
+		goto error;
+	}
+
+	group = cfg_lookup_group(group_name->s, group_name->len);
+	if (!group) {
+		/* create a new group with NULL values, it will be filled in later */
+		group = cfg_new_group(group_name->s, group_name->len,
+					0 /* num */, NULL /* mapping */,
+					NULL /* vars */, 0 /* size */, NULL /* handle */);
+
+		if (!group)
+			goto error;
+		/* It is not yet known whether the group will be static or dynamic */
+		group->dynamic = CFG_GROUP_UNKNOWN;
+	}
+
+	add_var = (cfg_add_var_t *)pkg_malloc(sizeof(cfg_add_var_t) +
+						(type ? (var_name->len - 1) : 0));
+	if (!add_var) {
+		LOG(L_ERR, "ERROR: new_add_var(): Not enough memory\n");
+		goto error;
+	}
+	memset(add_var, 0, sizeof(cfg_add_var_t) +
+				(type ? (var_name->len - 1) : 0));
+
+	add_var->group_id = group_id;
+	if (type) {
+		add_var->name_len = var_name->len;
+		memcpy(add_var->name, var_name->s, var_name->len);
+
+		switch (type) {
+		case CFG_VAR_INT:
+			add_var->val.i = (int)(long)val;
+			break;
+
+		case CFG_VAR_STR:
+			len = ((str *)val)->len;
+			add_var->val.s.s = (char *)pkg_malloc(sizeof(char) * len);
+			if (!add_var->val.s.s) {
+				LOG(L_ERR, "ERROR: new_add_var(): Not enough memory\n");
+				goto error;
+			}
+			add_var->val.s.len = len;
+			memcpy(add_var->val.s.s, ((str *)val)->s, len);
+			break;
+
+		case CFG_VAR_STRING:
+			len = strlen((char *)val);
+			add_var->val.ch = (char *)pkg_malloc(sizeof(char) * (len + 1));
+			if (!add_var->val.ch) {
+				LOG(L_ERR, "ERROR: new_add_var(): Not enough memory\n");
+				goto error;
+			}
+			memcpy(add_var->val.ch, (char *)val, len);
+			add_var->val.ch[len] = '\0';
+			break;
+
+		default:
+			LOG(L_ERR, "ERROR: new_add_var(): unsupported value type: %u\n",
+				type);
+			goto error;
+		}
+		add_var->type = type;
+	}
+
+	/* order the list by group_id, it will be easier to count the group instances */
+	for(	add_var_p = &group->add_var;
+		*add_var_p && ((*add_var_p)->group_id <= group_id);
+		add_var_p = &((*add_var_p)->next));
+
+	add_var->next = *add_var_p;
+	*add_var_p = add_var;
+
+	return 0;
+
+error:
+	if (!type)
+		LOG(L_ERR, "ERROR: new_add_var(): failed to add the additional group instance: %.*s[%u]\n",
+			group_name->len, group_name->s, group_id);
+	else
+		LOG(L_ERR, "ERROR: new_add_var(): failed to add the additional variable instance: %.*s[%u].%.*s\n",
+			group_name->len, group_name->s, group_id,
+			var_name->len, var_name->s);
+
+	if (add_var)
+		pkg_free(add_var);
+	return -1;
+}
+
+/* delete the additional variable list */
+static void del_add_var_list(cfg_group_t *group)
+{
+	cfg_add_var_t	*add_var, *add_var2;
+
+	add_var = group->add_var;
+	while (add_var) {
+		add_var2 = 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);
+		add_var = add_var2;
+	}
+	group->add_var = NULL;
+}
+
+/* create the array of additional group instances from the linked list */
+static int apply_add_var_list(cfg_block_t *block, cfg_group_t *group)
+{
+	int		i, num, size;
+	unsigned int	group_id;
+	cfg_add_var_t	*add_var;
+	cfg_group_inst_t	*new_array, *ginst;
+
+	/* count the number of group instances */
+	for (	add_var = group->add_var, num = 0, group_id = 0;
+		add_var;
+		add_var = add_var->next
+	) {
+		if (!num || (group_id != add_var->group_id)) {
+			num++;
+			group_id = add_var->group_id;
+		}
+	}
+
+	if (!num)	/* nothing to do */
+		return 0;
+
+	LOG(L_DBG, "DEBUG: apply_add_var_list(): creating the group instance array "
+		"for '%.*s' with %d slots\n",
+		group->name_len, group->name, num);
+	size = (sizeof(cfg_group_inst_t) + group->size - 1) * num;
+	new_array = (cfg_group_inst_t *)shm_malloc(size);
+	if (!new_array) {
+		LOG(L_ERR, "ERROR: apply_add_var_list(): not enough shm memory\n");
+		return -1;
+	}
+	memset(new_array, 0, size);
+
+	for (i = 0; i < num; i++) {
+		/* Go though each group instance, set the default values,
+		and apply the changes */
+
+		if (!group->add_var) {
+			LOG(L_ERR, "BUG: apply_add_var_list(): no more additional variable left\n");
+			goto error;
+		}
+		ginst = (cfg_group_inst_t *)((char*)new_array + (sizeof(cfg_group_inst_t) + group->size - 1) * i);
+		ginst->id = group->add_var->group_id;
+		/* fill in the new group instance with the default data */
+		memcpy(	ginst->vars,
+			CFG_GROUP_DATA(block, group),
+			group->size);
+		/* cfg_apply_list() moves the group->add_var pointer to
+		the beginning of the new group instance. */
+		if (cfg_apply_list(ginst, group, ginst->id, &group->add_var))
+			goto error;
+	}
+
+#ifdef EXTRA_DEBUG
+	if (group->add_var) {
+		LOG(L_ERR, "BUG: apply_add_var_list(): not all the additional variables have been consumed\n");
+		goto error;
+	}
+#endif
+
+	CFG_GROUP_META(block, group)->num = num;
+	CFG_GROUP_META(block, group)->array = new_array;
+	return 0;
+
+error:
+	LOG(L_ERR, "ERROR: apply_add_var_list(): Failed to apply the additional variable list\n");
+	shm_free(new_array);
+	return -1;
+}
+
+/* Move the group handle to the specified group instance pointed by dst_ginst.
+ * src_ginst shall point to the active group instance.
+ * Both parameters can be NULL meaning that the src/dst config is the default, 
+ * not an additional group instance.
+ * The function executes all the per-child process callbacks which are different
+ * in the two instaces.
+ */
+void cfg_move_handle(cfg_group_t *group, cfg_group_inst_t *src_ginst, cfg_group_inst_t *dst_ginst)
+{
+	cfg_mapping_t		*var;
+	unsigned int		bitmap;
+	int			i, pos;
+	str			gname, vname;
+
+	if (src_ginst == dst_ginst)
+		return;	/* nothing to do */
+
+	/* move the handle to the variables of the dst group instance,
+	or to the local config if no dst group instance is specified */
+	*(group->handle) = dst_ginst ?
+				dst_ginst->vars
+				: CFG_GROUP_DATA(cfg_local, group);
+
+	/* call the per child process callback of those variables
+	that have different value in the two group instances */
+	/* TODO: performance optimization: this entire loop can be
+	skipped if the group does not have any variable with
+	per-child process callback. Use some flag in the group
+	structure for this purpose. */
+	gname.s = group->name;
+	gname.len = group->name_len;
+	for (i = 0; i < CFG_MAX_VAR_NUM/(sizeof(int)*8); i++) {
+		bitmap = ((src_ginst) ? src_ginst->set[i] : 0U)
+			| ((dst_ginst) ? dst_ginst->set[i] : 0U);
+		while (bitmap) {
+			pos = bit_scan_forward32(bitmap);
+			var = &group->mapping[pos + i*sizeof(int)*8];
+			if (var->def->on_set_child_cb) {
+				vname.s = var->def->name;
+				vname.len = var->name_len;
+				var->def->on_set_child_cb(&gname, &vname);
+			}
+			bitmap -= (1U << pos);
+		}
+	}
+	/* keep track of how many group instences are set in the child process */
+	if (!src_ginst && dst_ginst)
+		cfg_ginst_count++;
+	else if (!dst_ginst)
+		cfg_ginst_count--;
+#ifdef EXTRA_DEBUG
+	if (cfg_ginst_count < 0)
+		LOG(L_ERR, "ERROR: cfg_select(): BUG, cfg_ginst_count is negative: %d. group=%.*s\n",
+			cfg_ginst_count, group->name_len, group->name);
+#endif
+	return;
+}
+
+/* Move the group handle to the specified group instance. */
+int cfg_select(cfg_group_t *group, unsigned int id)
+{
+	cfg_group_inst_t	*ginst;
+
+	if (!(ginst = cfg_find_group(CFG_GROUP_META(cfg_local, group),
+				group->size,
+				id))
+	) {
+		LOG(L_ERR, "ERROR: cfg_select(): group instance '%.*s[%u]' does not exist\n",
+				group->name_len, group->name, id);
+		return -1;
+	}
+
+	cfg_move_handle(group,
+			CFG_HANDLE_TO_GINST(*(group->handle)), /* the active group instance */
+			ginst);
+
+	LOG(L_DBG, "DEBUG: cfg_select(): group instance '%.*s[%u]' has been selected\n",
+			group->name_len, group->name, id);
+	return 0;
+}
+
+/* Reset the group handle to the default, local configuration */
+int cfg_reset(cfg_group_t *group)
+{
+	cfg_move_handle(group,
+			CFG_HANDLE_TO_GINST(*(group->handle)), /* the active group instance */
+			NULL);
+
+	LOG(L_DBG, "DEBUG: cfg_reset(): default group '%.*s' has been selected\n",
+			group->name_len, group->name);
+	return 0;
+}

+ 180 - 7
cfg/cfg_struct.h

@@ -34,21 +34,50 @@
 #include "../mem/shm_mem.h"
 #include "../locking.h"
 #include "../compiler_opt.h"
+#include "../bit_test.h"
 #include "cfg.h"
 
+/*! \brief Maximum number of variables within a configuration group. */
+#define CFG_MAX_VAR_NUM	256
+
 /*! \brief indicates that the variable has been already shmized */
 #define cfg_var_shmized	1U
 
+/*! \brief Structure for storing additional values of a variable.
+ * When the config is shmzied, these variables are combined in
+ * an array.
+ */
+typedef struct _cfg_add_var {
+	struct _cfg_add_var	*next;
+	unsigned int	type;	/*!< type == 0 is also valid, it indicates that the group
+				must be created with the default values */
+	union {
+		char	*ch;
+		str	s;
+		int	i;
+	} val;
+	unsigned int	group_id; /*!< Id of the group instance */
+	int		name_len;	/*!< Name of the variable. The variable may not be known,
+					for example the additional group value is set in the script
+					before the cfg group is declared. Hence, the pointer cannot
+					be stored here. */
+	char		name[1];
+} cfg_add_var_t;
+
 /*! \brief structure used for variable - pointer mapping */
 typedef struct _cfg_mapping {
 	cfg_def_t	*def;		/*!< one item of the cfg structure definition */
 	int		name_len;	/*!< length of def->name */
 
 	/* additional information about the cfg variable */
+	int		pos;	/*!< position of the variable within the group starting from 0 */
 	int		offset; /*!< offest within the memory block */
 	unsigned int	flag;	/*!< flag indicating the state of the variable */
 } cfg_mapping_t;
 
+/*! \brief type of the group */
+enum { CFG_GROUP_UNKNOWN = 0, CFG_GROUP_DYNAMIC, CFG_GROUP_STATIC };
+
 /*! \brief linked list of registered groups */
 typedef struct _cfg_group {
 	int		num;		/*!< number of variables within the group */
@@ -57,10 +86,15 @@ typedef struct _cfg_group {
 	char		*vars;		/*!< pointer to the memory block where the values
 					are stored -- used only before the config is
 					shmized. */
+	cfg_add_var_t	*add_var;	/*!< Additional instances of the variables.
+					This linked list is used only before the config is
+					shmized. */
 	int		size;		/*!< size of the memory block that has to be
 					allocated to store the values */
-	int		offset;		/*!< offset of the group within the
-					shmized memory block */
+	int		meta_offset;	/*!< offset of the group within the
+					shmized memory block for the meta_data */
+	int		var_offset;	/*!< offset of the group within the
+					shmized memory block for the variables */
 	void		**handle;	/*!< per-process handle that can be used
 					by the modules to access the variables.
 					It is registered when the group is created,
@@ -69,16 +103,37 @@ typedef struct _cfg_group {
 	unsigned char	dynamic;	/*!< indicates whether the variables within the group
 					are dynamically	allocated or not */
 	struct _cfg_group	*next;
-	int		name_len;	
+	int		name_len;
 	char		name[1];
 } cfg_group_t;
 
+/*! \brief One instance of the cfg group variables which stores
+ * the additional values. These values can overwrite the default values. */
+typedef struct _cfg_group_inst {
+	unsigned int	id;		/*!< identifier of the group instance */
+	unsigned int	set[CFG_MAX_VAR_NUM/(sizeof(int)*8)];
+					/*!< Bitmap indicating whether or not a value is explicitely set
+					within this instance. If the value is not set,
+					then the default value is used, and copied into this instance. */
+	unsigned char	vars[1];	/*!< block for the values */
+} cfg_group_inst_t;
+
+/*! \bried Meta-data which is stored before each variable group
+ * within the blob. This structure is used to handle the multivalue
+ * instances of the variables, i.e. manages the array for the
+ * additional values. */
+typedef struct _cfg_group_meta {
+	int			num;	/*!< Number of items in the array */
+	cfg_group_inst_t	*array;	/*!< Array of cfg groups with num number of items */
+} cfg_group_meta_t;
+
 /*! \brief single memoy block that contains all the cfg values */
 typedef struct _cfg_block {
 	atomic_t	refcnt;		/*!< reference counter,
 					the block is automatically deleted
 					when it reaches 0 */
-	char		**replaced;	/*!< set of the strings that must be freed
+	void		**replaced;	/*!< set of the strings and other memory segments
+					that must be freed
 					together with the block. The content depends
 					on the block that replaces this one */
 	unsigned char	vars[1];	/*!< blob that contains the values */
@@ -108,12 +163,14 @@ typedef struct _cfg_child_cb {
 extern cfg_group_t	*cfg_group;
 extern cfg_block_t	**cfg_global;
 extern cfg_block_t	*cfg_local;
+extern int		cfg_block_size;
 extern gen_lock_t	*cfg_global_lock;
 extern gen_lock_t	*cfg_writer_lock;
 extern int		cfg_shmized;
 extern cfg_child_cb_t	**cfg_child_cb_first;
 extern cfg_child_cb_t	**cfg_child_cb_last;
 extern cfg_child_cb_t	*cfg_child_cb;
+extern int		cfg_ginst_count;
 
 /* magic value for cfg_child_cb for processes that do not want to
    execute per-child callbacks */
@@ -123,6 +180,33 @@ extern cfg_child_cb_t	*cfg_child_cb;
 #define CFG_VAR_TYPE(var)	CFG_VAR_MASK((var)->def->type)
 #define CFG_INPUT_TYPE(var)	CFG_INPUT_MASK((var)->def->type)
 
+/* get the meta-data of a group from the block */
+#define CFG_GROUP_META(block, group) \
+	((cfg_group_meta_t *)((block)->vars + (group)->meta_offset))
+
+/* get the data block of a group from the block */
+#define CFG_GROUP_DATA(block, group) \
+	((unsigned char *)((block)->vars + (group)->var_offset))
+
+/* Test whether a variable is explicitely set in the group instance,
+ * or it uses the default value */
+#define CFG_VAR_TEST(group_inst, var) \
+	bit_test((var)->pos % (sizeof(int)*8), (group_inst)->set + (var)->pos/(sizeof(int)*8))
+
+/* Test whether a variable is explicitely set in the group instance,
+ * or it uses the default value, and set the flag. */
+#define CFG_VAR_TEST_AND_SET(group_inst, var) \
+	bit_test_and_set((var)->pos % (sizeof(int)*8), (group_inst)->set + (var)->pos/(sizeof(int)*8))
+
+/* Return the group instance pointer from a handle,
+ * or NULL if the handle points to the default configuration block */
+#define CFG_HANDLE_TO_GINST(h) \
+	( (((unsigned char*)(h) < cfg_local->vars) \
+		|| ((unsigned char*)(h) > cfg_local->vars + cfg_block_size) \
+	) ? \
+		(cfg_group_inst_t*)((char*)(h) - (unsigned long)&((cfg_group_inst_t *)0)->vars) \
+		: NULL )
+
 /* initiate the cfg framework */
 int sr_cfg_init(void);
 
@@ -173,6 +257,11 @@ cfg_group_t *cfg_new_group(char *name, int name_len,
 		int num, cfg_mapping_t *mapping,
 		char *vars, int size, void **handle);
 
+/* Set the values of an existing cfg group. */
+void cfg_set_group(cfg_group_t *group,
+		int num, cfg_mapping_t *mapping,
+		char *vars, int size, void **handle);
+
 /* copy the variables to shm mem */
 int cfg_shmize(void);
 
@@ -190,6 +279,16 @@ static inline void cfg_block_free(cfg_block_t *block)
 	shm_free(block);
 }
 
+/* Move the group handle to the specified group instance pointed by dst_ginst.
+ * src_ginst shall point to the active group instance.
+ * Both parameters can be NULL meaning that the src/dst config is the default, 
+ * not an additional group instance.
+ * The function executes all the per-child process callbacks which are different
+ * in the two instaces.
+ */
+void cfg_move_handle(cfg_group_t *group, cfg_group_inst_t *src_ginst, cfg_group_inst_t *dst_ginst);
+
+
 /* lock and unlock the global cfg block -- used only at the
  * very last step when the block is replaced */
 #define CFG_LOCK()	lock_get(cfg_global_lock);
@@ -242,7 +341,7 @@ static inline void cfg_update_local(int no_cbs)
 		group;
 		group = group->next
 	)
-		*(group->handle) = cfg_local->vars + group->offset;
+		*(group->handle) = CFG_GROUP_DATA(cfg_local, group);
 
 	if (unlikely(cfg_child_cb==CFG_NO_CHILD_CBS || no_cbs))
 		return;
@@ -274,6 +373,29 @@ static inline void cfg_update_local(int no_cbs)
 	}
 }
 
+/* Reset all the group handles to the default, local configuration */
+static inline void cfg_reset_handles(void)
+{
+	cfg_group_t	*group;
+
+	if (!cfg_local)
+		return;
+
+	for (	group = cfg_group;
+		group && cfg_ginst_count; /* cfg_ginst_count is decreased every time
+					a group handle is reset. When it reaches 0,
+					needless to continue the loop */
+		group = group->next
+	) {
+		if (((unsigned char*)*(group->handle) < cfg_local->vars)
+			|| ((unsigned char*)*(group->handle) > cfg_local->vars + cfg_block_size)
+		)
+			cfg_move_handle(group,
+					CFG_HANDLE_TO_GINST(*(group->handle)),
+					NULL);
+	}
+}
+
 /* sets the local cfg block to the active block
  * 
  * If your module forks a new process that implements
@@ -283,6 +405,8 @@ static inline void cfg_update_local(int no_cbs)
  */
 #define cfg_update() \
 	do { \
+		if (unlikely(cfg_ginst_count)) \
+			cfg_reset_handles(); \
 		if (unlikely(cfg_local != *cfg_global)) \
 			cfg_update_local(0); \
 	} while(0)
@@ -297,6 +421,16 @@ static inline void cfg_update_local(int no_cbs)
 			cfg_update_local(1); \
 	} while(0)
 
+/* Reset all the group handles in the child process,
+ * i.e. move them back to the default local configuration.
+ */
+#define cfg_reset_all() \
+	do { \
+		if (unlikely(cfg_ginst_count)) \
+			cfg_reset_handles(); \
+	} while(0)
+
+
 /* searches a group by name */
 cfg_group_t *cfg_lookup_group(char *name, int len);
 	
@@ -304,12 +438,36 @@ cfg_group_t *cfg_lookup_group(char *name, int len);
 int cfg_lookup_var(str *gname, str *vname,
 			cfg_group_t **group, cfg_mapping_t **var);
 
-/* clones the global config block */
+/* searches a variable definition within a group by its name */
+cfg_mapping_t *cfg_lookup_var2(cfg_group_t *group, char *name, int len);
+
+/* clones the global config block
+ * WARNING: unsafe, cfg_writer_lock or cfg_global_lock must be held!
+ */
 cfg_block_t *cfg_clone_global(void);
 
+/* Clone an array of configuration group instances. */
+cfg_group_inst_t *cfg_clone_array(cfg_group_meta_t *meta, cfg_group_t *group);
+
+/* Extend the array of configuration group instances with one more instance.
+ * Only the ID of the new group is set, nothing else. */
+cfg_group_inst_t *cfg_extend_array(cfg_group_meta_t *meta, cfg_group_t *group,
+				unsigned int group_id,
+				cfg_group_inst_t **new_group);
+
+/* Remove an instance from a group array.
+ * inst must point to an instance within meta->array.
+ * *_new_array is set to the newly allocated array. */
+int cfg_collapse_array(cfg_group_meta_t *meta, cfg_group_t *group,
+				cfg_group_inst_t *inst,
+				cfg_group_inst_t **_new_array);
+
 /* clones a string to shared memory */
 int cfg_clone_str(str *src, str *dst);
 
+/* Find the group instance within the meta-data based on the group_id */
+cfg_group_inst_t *cfg_find_group(cfg_group_meta_t *meta, int group_size, unsigned int group_id);
+
 /* append new callbacks to the end of the child callback list
  *
  * WARNING: the function is unsafe, either hold CFG_LOCK(),
@@ -324,7 +482,7 @@ void cfg_install_child_cb(cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last);
  * cb_first and cb_last define a linked list of per-child process
  * callbacks. This list is added to the global linked list.
  */
-void cfg_install_global(cfg_block_t *block, char **replaced,
+void cfg_install_global(cfg_block_t *block, void **replaced,
 			cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last);
 
 /* creates a structure for a per-child process callback */
@@ -335,4 +493,19 @@ cfg_child_cb_t *cfg_child_cb_new(str *gname, str *name,
 /* free the memory allocated for a child cb list */
 void cfg_child_cb_free(cfg_child_cb_t *child_cb_first);
 
+/* Allocate memory for a new additional variable
+ * and link it to a configuration group.
+ * type==0 results in creating a new group instance with the default values.
+ * The group is created with CFG_GROUP_UNKNOWN type if it does not exist.
+ * Note: this function is usable only before the configuration is shmized.
+ */
+int new_add_var(str *group_name, unsigned int group_id, str *var_name,
+				void *val, unsigned int type);
+
+/* Move the group handle to the specified group instance. */
+int cfg_select(cfg_group_t *group, unsigned int id);
+
+/* Reset the group handle to the default, local configuration */
+int cfg_reset(cfg_group_t *group);
+
 #endif /* _CFG_STRUCT_H */

+ 60 - 1
doc/cfg.txt

@@ -26,6 +26,15 @@ without the need of commit. That means a kind of transaction support,
 the framework can keep track of the changes (per driver) until they
 are committed or rolled-back.
 
+The framework also supports multiple versions of the core or module
+configurations. Every SIP message processing or timer function starts with
+the default version which can be changed runtime in the script. Hence, even if
+the core/module implements a variable with a single value, it may have multiple
+instances with different values in memory, and the configuration instances can be
+swapped runtime. New instances of a configuration group can be added and deleted
+runtime by the drivers, and all the variables in the group instances take
+the default value unless their value has been explicitely set.
+
 2. Using the framework in a module
 ===============================================================================
 
@@ -130,6 +139,8 @@ Each row consists of the following items:
 				of the cfg framework. By default this callback is
 				called by all the child processes separately,
 				this can be changed with this flag.
+				Multiple values are not supported together with
+				the CFG_CB_ONLY_ONCE flag.
 
 - minimum value for integers (optional)
 - maximum value for integers (optional)
@@ -323,10 +334,11 @@ void		*h;
 str		gname, vname;
 void		*old_val, *new_val;
 unsigned int	val_type;
+unsigned int	*group_id;
 
 if (cfg_diff_init(ctx, &h)) return -1;
 while(cfg_diff_next(&h,
-		&gname, &vname,
+		&gname, &group_id, &vname,
 		&old_val, &new_val,
 		&val_type)
 ) {
@@ -334,6 +346,11 @@ while(cfg_diff_next(&h,
 }
 cfg_diff_release(ctx);
 
+-------------------------------------------------------------------------------
+9. Add/delete an instance of an existing group:
+
+cfg_add_group_inst()
+cfg_del_group_inst()
 
 5. Refreshing the configuration
 ===============================================================================
@@ -424,3 +441,45 @@ New configuration values can be declared in the script, the syntax is:
 The values can be accessed via select calls:
 
 @cfg_get.<group_name>.<var_name>
+
+
+Use the following syntax to set an additional instance of a configuration value:
+
+<group_name>[id].<var_name> = <value>
+
+id is an unsigned integer starting from 0, it does not have to be continuous.
+Note, that not the variables but the entire configuration group can have multiple
+instances, and it is possible to swap the configuration of the entire group at once
+with cfg_select("group_name", id), see the example below:
+
+custom.var1 = 1;
+custom.var2 = "default string";
+
+custom[1].var1 = 15;
+custom[1].var2 = "More specific string";
+
+custom[2].var1 = 3;
+# custom[2].var2 is not set, hence, it will inherit the value of custom.var2.
+# When custom.var2 changes, custom[2].var1 will be also updated.
+
+
+route {
+	# Actual values: var1:1, var2:"default string"
+
+	cfg_select("custom", 1);
+	# Actual values: var1:15, var2:"More specific string"
+
+	cfg_select("custom", 2");
+	# Actual values: var1:3, var2:"default string"
+
+	cfg_reset("custom")
+	# Actual values: var1:1, var2:"default string"
+}
+
+cfg_reset("group_name") can be used to reset the configuration back to the original values.
+The values are automatically reseted before each SIP message is started to be processed, or after
+each timer function execution.
+The above example with custom variables is supported also with module and core configuration
+groups. The only restriction is that variables with CFG_CB_ONLY_ONCE flag cannot have
+multiple values.
+

+ 3 - 3
modules/cfg_db/cfg_db.c

@@ -195,13 +195,13 @@ static int find_cfg_var(str *group_name, char *def_name, db_res_t *transl_res) {
 			/* read and set cfg var */
 			switch (rec->fld[0].type) {
 				case DB_STR:
-					if (cfg_set_now(cfg_ctx, group_name, &def_name_s, &rec->fld[0].v.lstr, CFG_VAR_STR) < 0) goto err;
+					if (cfg_set_now(cfg_ctx, group_name, NULL /* group id */, &def_name_s, &rec->fld[0].v.lstr, CFG_VAR_STR) < 0) goto err;
 					break;
 				case DB_CSTR:					
-					if (cfg_set_now_string(cfg_ctx, group_name, &def_name_s, rec->fld[0].v.cstr) < 0) goto err;
+					if (cfg_set_now_string(cfg_ctx, group_name, NULL /* group id */, &def_name_s, rec->fld[0].v.cstr) < 0) goto err;
 					break;
 				case DB_INT:
-					if (cfg_set_now_int(cfg_ctx, group_name, &def_name_s, rec->fld[0].v.int4) < 0) goto err;
+					if (cfg_set_now_int(cfg_ctx, group_name, NULL /* group id */, &def_name_s, rec->fld[0].v.int4) < 0) goto err;
 					break;
 				default:
 					ERR(MODULE_NAME": unexpected field type (%d), table:'%s', field:'%s'\n", 

+ 43 - 17
modules/cfg_rpc/README

@@ -4,7 +4,7 @@ Miklos Tirpak
 
    <[email protected]>
 
-   Copyright © 2007 iptelorg GmbH
+   Copyright © 2007 iptelorg GmbH
      __________________________________________________________________
 
    1.1. Overview
@@ -20,27 +20,53 @@ Miklos Tirpak
 1.2. RPC Interface
 
    The module implements the following RPC interface commands:
-     * cfg_rpc.set_now_int - Set the value of a configuration variable and
+     * cfg.set_now_int - Set the value of a configuration variable and
        commit the change immediately. The function accepts three
-       parameters: group name, variable name, integer value.
-     * cfg_rpc.set_now_string - Set the value of a configuration variable
-       and commit the change immediately. The function accepts three
-       parameters: group name, variable name, string value.
-     * cfg_rpc.set_delayed_int - Prepare the change of a configuration
+       parameters: group name, variable name, integer value. The group
+       name can optionally contain the group instance id, for example
+       foo[5].
+     * cfg.set_now_string - Set the value of a configuration variable and
+       commit the change immediately. The function accepts three
+       parameters: group name, variable name, string value. The group name
+       can optionally contain the group instance id, for example foo[5].
+     * cfg.set - Set the value of a configuration variable and commit the
+       change immediately. This is a wrapper command for cfg.set_now_int
+       and cfg.set_now_string depending on the type of the value provided.
+       The function accepts three parameters: group name, variable name,
+       int/string value. The group name can optionally contain the group
+       instance id, for example foo[5].
+     * cfg.set_delayed_int - Prepare the change of a configuration
        variable, but does not commit the new value yet. The function
        accepts three parameters: group name, variable name, integer value.
-     * cfg_rpc.set_delayed_string - Prepare the change of a configuration
+       The group name can optionally contain the group instance id, for
+       example foo[5].
+     * cfg.set_delayed_string - Prepare the change of a configuration
        variable, but does not commit the new value yet. The function
        accepts three parameters: group name, variable name, string value.
-     * cfg_rpc.commit - Commit the previously prepared configuration
-       changes. The function does not have any parameters.
-     * cfg_rpc.rollback - Drop the prepared configuration changes. The
+       The group name can optionally contain the group instance id, for
+       example foo[5].
+     * cfg.set_delayed - Prepare the change of a configuration variable,
+       but does not commit the new value yet. This is a wrapper command
+       for cfg.set_delayed_int and cfg.set_delayed_string depending on the
+       type of the value provided. The function accepts three parameters:
+       group name, variable name, int/string value. The group name can
+       optionally contain the group instance id, for example foo[5].
+     * cfg.commit - Commit the previously prepared configuration changes.
+       The function does not have any parameters.
+     * cfg.rollback - Drop the prepared configuration changes. The
        function does not have any parameters.
-     * cfg_rpc.get - Get the value of a configuration variable. The
+     * cfg.get - Get the value of a configuration variable. The function
+       accepts two parameters: group name, variable name. The group name
+       can optionally contain the group instance id, for example foo[5].
+     * cfg.help - Print the description of a configuration variable. The
        function accepts two parameters: group name, variable name.
-     * cfg_rpc.help - Print the description of a configuration variable.
-       The function accepts two parameters: group name, variable name.
-     * cfg_rpc.list - List the configuration variables. The function does
-       not have any parameters.
-     * cfg_rpc.diff - List the pending configuration changes that have not
+     * cfg.list - List the configuration variables. The function has one
+       optional parameter: group name.
+     * cfg.diff - List the pending configuration changes that have not
        been committed yet. The function does not have any parameters.
+     * cfg.add_group_inst - Add a new instance to an existing
+       configuration group. The function accepts one parameter: group
+       name[instance id], for example foo[5].
+     * cfg.del_group_inst - Delete an instance of an existing
+       configuration group. The function accepts one parameter: group
+       name[instance id], for example foo[5].

+ 205 - 13
modules/cfg_rpc/cfg_rpc.c

@@ -49,6 +49,40 @@ static int mod_init(void)
 	return 0;
 }
 
+/* Set the group_id pointer based on the group string.
+ * The string is either "group_name", or "group_name[group_id]"
+ * *group_id is set to null in the former case.
+ * Warning: changes the group string
+ */
+static int get_group_id(str *group, unsigned int **group_id)
+{
+	static unsigned int	id;
+	str	s;
+
+	if (!group->s || (group->s[group->len-1] != ']')) {
+		*group_id = NULL;
+		return 0;
+	}
+
+	s.s = group->s + group->len - 2;
+	s.len = 0;
+	while ((s.s > group->s) && (*s.s != '[')) {
+		s.s--;
+		s.len++;
+	}
+	if (s.s == group->s) /* '[' not found */
+		return -1;
+	group->len = s.s - group->s;
+	s.s++;
+	if (!group->len || !s.len)
+		return -1;
+	if (str2int(&s, &id))
+		return -1;
+
+	*group_id = &id;
+	return 0;
+}
+
 static const char* rpc_set_now_doc[2] = {
         "Set the value of a configuration variable and commit the change immediately",
         0
@@ -58,11 +92,17 @@ static void rpc_set_now_int(rpc_t* rpc, void* c)
 {
 	str	group, var;
 	int	i;
+	unsigned int	*group_id;
 
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 		return;
 
-	if (cfg_set_now_int(ctx, &group, &var, i)) {
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (cfg_set_now_int(ctx, &group, group_id, &var, i)) {
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 	}
@@ -72,11 +112,45 @@ static void rpc_set_now_string(rpc_t* rpc, void* c)
 {
 	str	group, var;
 	char	*ch;
+	unsigned int	*group_id;
 
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 		return;
 
-	if (cfg_set_now_string(ctx, &group, &var, ch)) {
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (cfg_set_now_string(ctx, &group, group_id, &var, ch)) {
+		rpc->fault(c, 400, "Failed to set the variable");
+		return;
+	}
+}
+
+static void rpc_set(rpc_t* rpc, void* c)
+{
+	str	group, var;
+	int	i, err;
+	char	*ch;
+	unsigned int	*group_id;
+
+	if (rpc->scan(c, "SS", &group, &var) < 2)
+		return;
+
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (rpc->scan(c, "d", &i) == 1)
+		err = cfg_set_now_int(ctx, &group, group_id, &var, i);
+	else if (rpc->scan(c, "s", &ch) == 1)
+		err = cfg_set_now_string(ctx, &group, group_id, &var, ch);
+	else
+		return; /* error */
+
+	if (err) {
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 	}
@@ -91,11 +165,17 @@ static void rpc_set_delayed_int(rpc_t* rpc, void* c)
 {
 	str	group, var;
 	int	i;
+	unsigned int	*group_id;
 
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 		return;
 
-	if (cfg_set_delayed_int(ctx, &group, &var, i)) {
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (cfg_set_delayed_int(ctx, &group, group_id, &var, i)) {
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 	}
@@ -105,11 +185,45 @@ static void rpc_set_delayed_string(rpc_t* rpc, void* c)
 {
 	str	group, var;
 	char	*ch;
+	unsigned int	*group_id;
 
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 		return;
 
-	if (cfg_set_delayed_string(ctx, &group, &var, ch)) {
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (cfg_set_delayed_string(ctx, &group, group_id, &var, ch)) {
+		rpc->fault(c, 400, "Failed to set the variable");
+		return;
+	}
+}
+
+static void rpc_set_delayed(rpc_t* rpc, void* c)
+{
+	str	group, var;
+	int	i, err;
+	char	*ch;
+	unsigned int	*group_id;
+
+	if (rpc->scan(c, "SS", &group, &var) < 2)
+		return;
+
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	if (rpc->scan(c, "d", &i) == 1)
+		err = cfg_set_delayed_int(ctx, &group, group_id, &var, i);
+	else if (rpc->scan(c, "s", &ch) == 1)
+		err = cfg_set_delayed_string(ctx, &group, group_id, &var, ch);
+	else
+		return; /* error */
+
+	if (err) {
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 	}
@@ -152,11 +266,17 @@ static void rpc_get(rpc_t* rpc, void* c)
 	void	*val;
 	unsigned int	val_type;
 	int	ret;
+	unsigned int	*group_id;
 
 	if (rpc->scan(c, "SS", &group, &var) < 2)
 		return;
 
-	ret = cfg_get_by_name(ctx, &group, &var,
+	if (get_group_id(&group, &group_id)) {
+		rpc->fault(c, 400, "Wrong group syntax. Use either \"group\", or \"group[id]\"");
+		return;
+	}
+
+	ret = cfg_get_by_name(ctx, &group, group_id, &var,
 			&val, &val_type);
 	if (ret < 0) {
 		rpc->fault(c, 400, "Failed to get the variable");
@@ -233,11 +353,21 @@ static void rpc_list(rpc_t* rpc, void* c)
 	str		gname;
 	cfg_def_t	*def;
 	int		i;
+	str		group;
+
+	if (rpc->scan(c, "*S", &group) < 1) {
+		group.s = NULL;
+		group.len = 0;
+	}
 
 	cfg_get_group_init(&h);
 	while(cfg_get_group_next(&h, &gname, &def))
-		for (i=0; def[i].name; i++)
-			rpc->printf(c, "%.*s: %s", gname.len, gname.s, def[i].name);
+		if (!group.len
+			|| ((gname.len == group.len)
+				&& (memcmp(gname.s, group.s, group.len) == 0))
+		)
+			for (i=0; def[i].name; i++)
+				rpc->printf(c, "%.*s: %s", gname.len, gname.s, def[i].name);
 }
 
 static const char* rpc_diff_doc[2] = {
@@ -249,24 +379,32 @@ static void rpc_diff(rpc_t* rpc, void* c)
 {
 	void		*h;
 	str		gname, vname;
+	unsigned int	*gid;
 	void		*old_val, *new_val;
 	unsigned int	val_type;
 	void		*rpc_handle;
+	int		err;
 
 
 	if (cfg_diff_init(ctx, &h)) {
 		rpc->fault(c, 400, "Failed to get the changes");
 		return;
 	}
-	while(cfg_diff_next(&h,
-			&gname, &vname,
+	while((err = cfg_diff_next(&h,
+			&gname, &gid, &vname,
 			&old_val, &new_val,
-			&val_type)
+			&val_type)) > 0
 	) {
 		rpc->add(c, "{", &rpc_handle);
-		rpc->struct_add(rpc_handle, "SS",
-				"group name", &gname,
-				"variable name", &vname);
+		if (gid)
+			rpc->struct_add(rpc_handle, "SdS",
+					"group name", &gname,
+					"group id", *gid,
+					"variable name", &vname);
+		else
+			rpc->struct_add(rpc_handle, "SS",
+					"group name", &gname,
+					"variable name", &vname);
 
 		switch (val_type) {
 		case CFG_VAR_INT:
@@ -294,11 +432,63 @@ static void rpc_diff(rpc_t* rpc, void* c)
 		}
 	}
 	cfg_diff_release(ctx);
+	if (err)
+		rpc->fault(c, 400, "Failed to get the changes");
+}
+
+static const char* rpc_add_group_inst_doc[2] = {
+	"Add a new instance to an existing configuration group",
+	0
+};
+
+static void rpc_add_group_inst(rpc_t* rpc, void* c)
+{
+	str	group;
+	unsigned int	*group_id;
+
+	if (rpc->scan(c, "S", &group) < 1)
+		return;
+
+	if (get_group_id(&group, &group_id) || !group_id) {
+		rpc->fault(c, 400, "Wrong group syntax. Use \"group[id]\"");
+		return;
+	}
+
+	if (cfg_add_group_inst(ctx, &group, *group_id)) {
+		rpc->fault(c, 400, "Failed to add the group instance");
+		return;
+	}
+}
+
+static const char* rpc_del_group_inst_doc[2] = {
+	"Delte an instance of a configuration group",
+	0
+};
+
+static void rpc_del_group_inst(rpc_t* rpc, void* c)
+{
+	str	group;
+	unsigned int	*group_id;
+
+	if (rpc->scan(c, "S", &group) < 1)
+		return;
+
+	if (get_group_id(&group, &group_id) || !group_id) {
+		rpc->fault(c, 400, "Wrong group syntax. Use \"group[id]\"");
+		return;
+	}
+
+	if (cfg_del_group_inst(ctx, &group, *group_id)) {
+		rpc->fault(c, 400, "Failed to delete the group instance");
+		return;
+	}
 }
 
 static rpc_export_t rpc_calls[] = {
+	{"cfg.set",		rpc_set,		rpc_set_now_doc,	0},
 	{"cfg.set_now_int",	rpc_set_now_int,	rpc_set_now_doc,	0},
 	{"cfg.set_now_string",	rpc_set_now_string,	rpc_set_now_doc,	0},
+	{"cfg.set_delayed",	rpc_set_delayed,	rpc_set_delayed_doc,	0},
 	{"cfg.set_delayed_int",	rpc_set_delayed_int,	rpc_set_delayed_doc,	0},
 	{"cfg.set_delayed_string",	rpc_set_delayed_string,	rpc_set_delayed_doc,	0},
 	{"cfg.commit",		rpc_commit,		rpc_commit_doc,		0},
@@ -307,6 +497,8 @@ static rpc_export_t rpc_calls[] = {
 	{"cfg.help",		rpc_help,		rpc_help_doc,		0},
 	{"cfg.list",		rpc_list,		rpc_list_doc,		0},
 	{"cfg.diff",		rpc_diff,		rpc_diff_doc,		0},
+	{"cfg.add_group_inst",	rpc_add_group_inst,	rpc_add_group_inst_doc,	0},	
+	{"cfg.del_group_inst",	rpc_del_group_inst,	rpc_del_group_inst_doc,	0},	
 	{0, 0, 0, 0}
 };
 

+ 58 - 16
modules/cfg_rpc/doc/rpc.xml

@@ -12,76 +12,118 @@
     <itemizedlist>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.set_now_int</emphasis> - Set the value of
+		<emphasis>cfg.set_now_int</emphasis> - Set the value of
 		a configuration variable and commit the change immediately.
 		The function accepts three parameters: group name, variable
-		name, integer value.
+		name, integer value. The group name can optionally contain the
+		group instance id, for example foo[5].
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.set_now_string</emphasis> - Set the value of
+		<emphasis>cfg.set_now_string</emphasis> - Set the value of
 		a configuration variable and commit the change immediately.
 		The function accepts three parameters: group name, variable
-		name, string value.
+		name, string value. The group name can optionally contain the
+		group instance id, for example foo[5].
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.set_delayed_int</emphasis> - Prepare the change of
+		<emphasis>cfg.set</emphasis> - Set the value of
+		a configuration variable and commit the change immediately.
+		This is a wrapper command for cfg.set_now_int and cfg.set_now_string
+		depending on the type of the value provided.
+		The function accepts three parameters: group name, variable
+		name, int/string value. The group name can optionally contain the
+		group instance id, for example foo[5].
+	    </para>
+	</listitem>
+	<listitem>
+	    <para>
+		<emphasis>cfg.set_delayed_int</emphasis> - Prepare the change of
 		a configuration variable, but does not commit the new value yet.
 		The function accepts three parameters: group name, variable
-		name, integer value.
+		name, integer value. The group name can optionally contain the
+		group instance id, for example foo[5].
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.set_delayed_string</emphasis> - Prepare the change of
+		<emphasis>cfg.set_delayed_string</emphasis> - Prepare the change of
 		a configuration variable, but does not commit the new value yet.
 		The function accepts three parameters: group name, variable
-		name, string value.
+		name, string value. The group name can optionally contain the
+		group instance id, for example foo[5].
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.commit</emphasis> - Commit the previously
+		<emphasis>cfg.set_delayed</emphasis> - Prepare the change of
+		a configuration variable, but does not commit the new value yet.
+		This is a wrapper command for cfg.set_delayed_int and cfg.set_delayed_string
+		depending on the type of the value provided.
+		The function accepts three parameters: group name, variable
+		name, int/string value. The group name can optionally contain the
+		group instance id, for example foo[5].
+	    </para>
+	</listitem>
+	<listitem>
+	    <para>
+		<emphasis>cfg.commit</emphasis> - Commit the previously
 		prepared configuration changes. The function does not have
 		any parameters.
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.rollback</emphasis> - Drop the prepared
+		<emphasis>cfg.rollback</emphasis> - Drop the prepared
 		configuration changes. The function does not have any
 		parameters.
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.get</emphasis> - Get the value of
+		<emphasis>cfg.get</emphasis> - Get the value of
 		a configuration variable. The function accepts two parameters:
-		group name, variable name.
+		group name, variable name. The group name can optionally contain the
+		group instance id, for example foo[5].
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.help</emphasis> - Print the description of
+		<emphasis>cfg.help</emphasis> - Print the description of
 		a configuration variable. The function accepts two parameters:
 		group name, variable name.
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.list</emphasis> - List the configuration
-		variables. The function does not have any parameters.
+		<emphasis>cfg.list</emphasis> - List the configuration
+		variables. The function has one optional parameter:
+		group name.
 	    </para>
 	</listitem>
 	<listitem>
 	    <para>
-		<emphasis>cfg_rpc.diff</emphasis> - List the pending
+		<emphasis>cfg.diff</emphasis> - List the pending
 		configuration changes that have not been committed yet.
 		The function does not have any parameters.
 	    </para>
 	</listitem>
+	<listitem>
+	    <para>
+		<emphasis>cfg.add_group_inst</emphasis> - Add a new instance
+		to an existing configuration group. The function accepts one parameter:
+		group name[instance id], for example foo[5].
+	    </para>
+	</listitem>
+	<listitem>
+	    <para>
+		<emphasis>cfg.del_group_inst</emphasis> - Delete an instance
+		of an existing configuration group. The function accepts one parameter:
+		group name[instance id], for example foo[5].
+	    </para>
+	</listitem>
     </itemizedlist>
 </section>

+ 99 - 25
modules/ctl/binrpc_run.c

@@ -97,6 +97,8 @@ struct binrpc_ctx{
 	char* method;
 	struct binrpc_gc_block* gc; /**< garbage collection */
 	int replied;
+	int err_code;
+	str err_phrase;	/**< Leading zero must be included! */
 };
 
 
@@ -388,6 +390,10 @@ inline void destroy_binrpc_ctx(struct binrpc_ctx* ctx)
 		pkg_free(ctx->out.pkt.body);
 		ctx->out.pkt.body=0;
 	}
+	if (ctx->err_phrase.s){
+		pkg_free(ctx->err_phrase.s);
+		ctx->err_phrase.s=NULL;
+	}
 	binrpc_gc_collect(ctx);
 }
 
@@ -395,32 +401,23 @@ inline void destroy_binrpc_ctx(struct binrpc_ctx* ctx)
 
 #define MAX_FAULT_LEN 256
 #define FAULT_START_BUF (3 /* maxint*/+2/*max str header*/)
-static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...)
+static void _rpc_fault(struct binrpc_ctx* ctx, int code,
+			char *phrase, int phrase_len)
 {
-	char buf[MAX_FAULT_LEN];
 	static unsigned char fault_start[FAULT_START_BUF];
 	static unsigned char hdr[BINRPC_MAX_HDR_SIZE];
 	struct iovec v[3];
 	struct binrpc_pkt body;
 	int b_len;
-	va_list ap;
-	int len;
 	int hdr_len;
 	int err;
-	
+
 	if (ctx->replied){
 		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
 					" more then once\n", ctx->method?ctx->method:"");
 		return;
 	}
 	err=0;
-	va_start(ap, fmt);
-	len=vsnprintf(buf, MAX_FAULT_LEN, fmt, ap); /* ignore trunc. errors */
-	if ((len<0) || (len > MAX_FAULT_LEN))
-		len=MAX_FAULT_LEN-1;
-	va_end(ap);
-	
-	len++; /* vnsprintf doesn't include the terminating 0 */
 	err=binrpc_init_pkt(&body, fault_start, FAULT_START_BUF);
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_init_pkt error\n");
@@ -429,22 +426,22 @@ static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...)
 	/* adding a fault "manually" to avoid extra memcpys */
 	err=binrpc_addint(&body, code);
 	if (err<0){
-		LOG(L_ERR, "ERROR: rpc_fault: addint error\n");
+		LOG(L_ERR, "ERROR: _rpc_fault: addint error\n");
 		goto error;
 	}
-	err=binrpc_add_str_mark(&body, BINRPC_T_STR, len);
+	err=binrpc_add_str_mark(&body, BINRPC_T_STR, phrase_len);
 	if (err<0){
-		LOG(L_ERR, "ERROR: rpc_fault: add_str_mark error\n");
+		LOG(L_ERR, "ERROR: _rpc_fault: add_str_mark error\n");
 		goto error;
 	}
 	/*
-	err=binrpc_addfault(&body, code, buf, len); 
+	err=binrpc_addfault(&body, code, phrase, phrase_len);
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_addfault error\n");
 		goto error;
 	}*/
 	b_len=binrpc_pkt_len(&body);
-	err=hdr_len=binrpc_build_hdr(BINRPC_FAULT, b_len+len,
+	err=hdr_len=binrpc_build_hdr(BINRPC_FAULT, b_len+phrase_len,
 								ctx->in.ctx.cookie, hdr, BINRPC_MAX_HDR_SIZE);
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_build_hdr error\n");
@@ -454,25 +451,90 @@ static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...)
 	v[0].iov_len=hdr_len;
 	v[1].iov_base=body.body;
 	v[1].iov_len=b_len;
-	v[2].iov_base=buf;
-	v[2].iov_len=len;
+	v[2].iov_base=phrase;
+	v[2].iov_len=phrase_len;
 	if ((err=sock_send_v(ctx->send_h, v, 3))<0){
 		if (err==-2){
-			LOG(L_ERR, "ERROR: binrpc_fault: send failed: "
+			LOG(L_ERR, "ERROR: _rpc_fault: send failed: "
 					"datagram too big\n");
 			return;
 		}
-		LOG(L_ERR, "ERROR: binrpc_fault: send failed\n");
+		LOG(L_ERR, "ERROR: _rpc_fault: send failed\n");
 		return;
 	}
 	ctx->replied=1;
 	return;
 error:
-	LOG(L_ERR, "ERROR: binrpc_fault: binrpc_* failed with: %s (%d)\n",
+	LOG(L_ERR, "ERROR: _rpc_fault: binrpc_* failed with: %s (%d)\n",
 			binrpc_error(err), err);
 }
 
+static void rpc_fault(struct binrpc_ctx* ctx, int code, char* fmt, ...)
+{
+	char buf[MAX_FAULT_LEN];
+	va_list ap;
+	int len;
 
+	if (ctx->replied){
+		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
+					" more then once\n", ctx->method?ctx->method:"");
+		return;
+	}
+	va_start(ap, fmt);
+	len=vsnprintf(buf, MAX_FAULT_LEN, fmt, ap); /* ignore trunc. errors */
+	if ((len<0) || (len > MAX_FAULT_LEN))
+		len=MAX_FAULT_LEN-1;
+	va_end(ap);
+
+	len++; /* vnsprintf doesn't include the terminating 0 */
+	return _rpc_fault(ctx, code, buf, len);
+}
+
+/* Prepare the error reply without sending out the message */
+static int rpc_fault_prepare(struct binrpc_ctx* ctx, int code, char* fmt, ...)
+{
+	char buf[MAX_FAULT_LEN];
+	va_list ap;
+	int len;
+
+	if (ctx->replied){
+		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
+					" more then once\n", ctx->method?ctx->method:"");
+		return -1;
+	}
+	va_start(ap, fmt);
+	len=vsnprintf(buf, MAX_FAULT_LEN, fmt, ap); /* ignore trunc. errors */
+	if ((len<0) || (len > MAX_FAULT_LEN))
+		len=MAX_FAULT_LEN-1;
+	va_end(ap);
+
+	len++; /* vnsprintf doesn't include the terminating 0 */
+
+	ctx->err_code = code;
+	if (ctx->err_phrase.s)
+		pkg_free(ctx->err_phrase.s);
+	ctx->err_phrase.s = (char*)pkg_malloc(sizeof(char)*len);
+	if (!ctx->err_phrase.s) {
+		ctx->err_code = 0;
+		ctx->err_phrase.len = 0;
+		LOG(L_ERR, "ERROR: rpc_fault_prepare: not enough memory\n");
+		return -1;
+	}
+	memcpy(ctx->err_phrase.s, buf, len);
+	ctx->err_phrase.len = len;
+	return 0;
+}
+
+/* Reset the saved error code */
+static void rpc_fault_reset(struct binrpc_ctx* ctx)
+{
+	ctx->err_code = 0;
+	if (ctx->err_phrase.s) {
+		pkg_free(ctx->err_phrase.s);
+		ctx->err_phrase.s = NULL;
+		ctx->err_phrase.len = 0;
+	}
+}
 
 /* build the reply from the current body */
 static int rpc_send(struct binrpc_ctx* ctx)
@@ -596,14 +658,21 @@ int process_rpc_req(unsigned char* buf, int size, int* bytes_needed,
 	f_ctx.method=val.u.strval.s;
 	rpc_e->function(&binrpc_callbacks, &f_ctx);
 	if (f_ctx.replied==0){
+		if ((binrpc_pkt_len(&f_ctx.out.pkt)==0)
+			&& f_ctx.err_code && f_ctx.err_phrase.s
+		) {
+			_rpc_fault(&f_ctx, f_ctx.err_code,
+				f_ctx.err_phrase.s, f_ctx.err_phrase.len);
 		/* to get an error reply if the rpc handlers hasn't replied
 		 *  uncomment the following code:
-		 * if (binrpc_pkt_len(&f_ctx.out.pkt)==0){
+		 * } else if (binrpc_pkt_len(&f_ctx.out.pkt)==0){
 			rpc_fault(&f_ctx, 500, "internal server error: no reply");
 			LOG(L_ERR, "ERROR: rpc method %s hasn't replied\n",
 					val.u.strval.s);
-		}else  */
+		 */
+		} else {
 			rpc_send(&f_ctx);
+		}
 	}
 end:
 	*bytes_needed=0; /* full read */
@@ -749,6 +818,9 @@ static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...)
 	double d;
 	str* s;
 	
+	/* clear the previously saved error code */
+	rpc_fault_reset(ctx);
+
 	va_start(ap, fmt);
 	orig_fmt=fmt;
 	nofault = 0;
@@ -823,8 +895,10 @@ static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...)
 	va_end(ap);
 	return (int)(fmt-orig_fmt)-modifiers;
 error_read:
+	/* Do not immediately send out the error message, the user might retry the scan with
+	different parameters */
 	if(nofault==0 || ((err!=E_BINRPC_MORE_DATA) && (err!=E_BINRPC_EOP)))
-		rpc_fault(ctx, 400, "error at parameter %d: expected %s type but"
+		rpc_fault_prepare(ctx, 400, "error at parameter %d: expected %s type but"
 						" %s", ctx->in.record_no, rpc_type_name(v.type),
 						 binrpc_error(err));
 	/*

+ 2 - 2
modules/tls/tls_init.c

@@ -605,7 +605,7 @@ int init_tls_h(void)
 		s.s = "low_mem_threshold1";
 		s.len = strlen(s.s);
 		if (low_mem_threshold1 != cfg_get(tls, tls_cfg, low_mem_threshold1) &&
-				cfg_set_now_int(cfg_ctx, &tls_grp, &s, low_mem_threshold1)) {
+				cfg_set_now_int(cfg_ctx, &tls_grp, NULL /* group id */, &s, low_mem_threshold1)) {
 			ERR("failed to set tls.low_mem_threshold1 to %d\n",
 					low_mem_threshold1);
 			return -1;
@@ -613,7 +613,7 @@ int init_tls_h(void)
 		s.s = "low_mem_threshold2";
 		s.len = strlen(s.s);
 		if (low_mem_threshold2 != cfg_get(tls, tls_cfg, low_mem_threshold2) &&
-				cfg_set_now_int(cfg_ctx, &tls_grp, &s, low_mem_threshold2)) {
+				cfg_set_now_int(cfg_ctx, &tls_grp, NULL /* group id */, &s, low_mem_threshold2)) {
 			ERR("failed to set tls.low_mem_threshold1 to %d\n",
 					low_mem_threshold2);
 			return -1;

+ 14 - 0
modules/xmlrpc/xmlrpc.c

@@ -642,6 +642,15 @@ static int init_xmlrpc_reply(struct xmlrpc_reply* reply)
 	return 0;
 }
 
+/** Clear the XML-RPC reply code and sets it back to a success reply.
+ *
+ * @param reply XML-RPC reply structure to be cleared.
+ */
+static void clear_xmlrpc_reply(struct xmlrpc_reply* reply)
+{
+	reply->code = 200;
+	reply->reason = "OK";
+}
 
 
 /* if this a delayed reply context, and it's never been use before, fix it */
@@ -1453,6 +1462,9 @@ static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
 	va_list ap;
 
 	reply = &ctx->reply;
+	/* clear the previously saved error code */
+	clear_xmlrpc_reply(reply);
+
 	fmt_len = strlen(fmt);
 	va_start(ap, fmt);
 	modifiers=0;
@@ -1776,6 +1788,8 @@ static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
 	while(*fmt) {
 		member_name = va_arg(ap, char*);
 		reply = s->reply;
+		/* clear the previously saved error code */
+		clear_xmlrpc_reply(reply);
 		ret = find_member(&value, s->doc, s->struct_in, reply, member_name);
 		if (ret != 0) goto error;
 

+ 2 - 2
modules_k/kex/mi_core.c

@@ -328,7 +328,7 @@ static struct mi_root *mi_debug(struct mi_root *cmd, void *param)
 			return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
 		set = 1;
 	} else {
-		if(cfg_get_by_name(_kex_cfg_ctx, &group_name, &var_name, &vval,
+		if(cfg_get_by_name(_kex_cfg_ctx, &group_name, NULL /* group id */, &var_name, &vval,
 					&val_type)!=0)
 			return init_mi_tree( 500, MI_SSTR(MI_INTERNAL_ERR));
 		new_debug = (int)(long)vval;
@@ -346,7 +346,7 @@ static struct mi_root *mi_debug(struct mi_root *cmd, void *param)
 	}
 
 	if(set==1) {
-		cfg_set_now(_kex_cfg_ctx, &group_name, &var_name,
+		cfg_set_now(_kex_cfg_ctx, &group_name, NULL /* group id */, &var_name,
 				(void *)(long)new_debug, CFG_VAR_INT);
 	}
 

+ 55 - 0
route.c

@@ -94,6 +94,7 @@
 #include "ut.h"
 #include "rvalue.h"
 #include "switch.h"
+#include "cfg/cfg_struct.h"
 
 #define RT_HASH_SIZE	8 /* route names hash */
 
@@ -1149,6 +1150,60 @@ int fix_actions(struct action* a)
 					goto error;
 				}
 				break;
+			case CFG_SELECT_T:
+				if (t->val[1].type == RVE_ST) {
+					rve = t->val[1].u.data;
+					if (rve_is_constant(rve)) {
+						/* if expression is constant => evaluate it
+						   as integer and replace it with the corresp.
+						   int */
+						rv = rval_expr_eval(0, 0, rve);
+						if (rv == 0 ||
+								rval_get_int( 0, 0, &i, rv, 0) < 0 ) {
+							ERR("failed to fix constant rve");
+							if (rv) rval_destroy(rv);
+							ret = E_BUG;
+							goto error;
+						}
+						rval_destroy(rv);
+						rve_destroy(rve);
+						t->val[1].type = NUMBER_ST;
+						t->val[1].u.number = i;
+					} else {
+						/* expression is not constant => fixup &
+						   optimize it */
+						if ((ret=fix_rval_expr(rve))
+								< 0) {
+							ERR("rve fixup failed\n");
+							ret = E_BUG;
+							goto error;
+						}
+					}
+				} else if (t->val[1].type != NUMBER_ST) {
+					BUG("invalid subtype %d for cfg_select()\n",
+								t->val[1].type);
+					ret = E_BUG;
+					goto error;
+				}
+
+			case CFG_RESET_T:
+				if (t->val[0].type != STRING_ST) {
+					BUG("invalid subtype %d for cfg_select() or cfg_reset()\n",
+								t->val[0].type);
+					ret = E_BUG;
+					goto error;
+				}
+				tmp_p = (void *)cfg_lookup_group(t->val[0].u.string, strlen(t->val[0].u.string));
+				if (!tmp_p) {
+					ERR("configuration group \"%s\" not found\n",
+						t->val[0].u.string);
+					ret = E_SCRIPT;
+					goto error;
+				}
+				pkg_free(t->val[0].u.string);
+				t->val[0].u.data = tmp_p;
+				t->val[0].type = CFG_GROUP_ST;
+				break;
 			default:
 				/* no fixup required for the rest */
 				break;

+ 5 - 2
route_struct.h

@@ -115,7 +115,9 @@ enum action_type{
 		SET_FWD_NO_CONNECT_T,
 		SET_RPL_NO_CONNECT_T,
 		SET_FWD_CLOSE_T,
-		SET_RPL_CLOSE_T
+		SET_RPL_CLOSE_T,
+		CFG_SELECT_T,
+		CFG_RESET_T
 };
 /* parameter types for actions or types for expression right operands
    (WARNING right operands only, not working for left operands) */
@@ -130,7 +132,8 @@ enum _operand_subtype{
 		SELECT_UNFIXED_ST,
 		STRING_RVE_ST /* RVE converted to a string (fparam hack) */,
 		RVE_FREE_FIXUP_ST /* (str)RVE fixed up by a reversable fixup */,
-		FPARAM_DYN_ST /* temporary only (fparam hack) */
+		FPARAM_DYN_ST /* temporary only (fparam hack) */,
+		CFG_GROUP_ST
 };
 
 typedef enum _expr_l_type expr_l_type;

+ 4 - 0
timer.c

@@ -892,6 +892,8 @@ inline static void timer_list_expire(ticks_t t, struct timer_head* h
 #endif
 			UNLOCK_TIMER_LIST(); /* acts also as write barrier */ 
 				ret=tl->f(t, tl, tl->data);
+				/* reset the configuration group handles */
+				cfg_reset_all();
 				if (ret==0){
 					UNSET_RUNNING();
 					LOCK_TIMER_LIST();
@@ -1147,6 +1149,8 @@ void slow_timer_main()
 				SET_RUNNING_SLOW(tl);
 				UNLOCK_SLOW_TIMER_LIST();
 					ret=tl->f(*ticks, tl, tl->data);
+					/* reset the configuration group handles */
+					cfg_reset_all();
 					if (ret==0){
 						/* one shot */
 						UNSET_RUNNING_SLOW();