Browse Source

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 years ago
parent
commit
b5cc26d622
23 changed files with 2254 additions and 230 deletions
  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
 #endif
 #include "switch.h"
 #include "switch.h"
 #include "events.h"
 #include "events.h"
+#include "cfg/cfg_struct.h"
 
 
 #include <sys/types.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/socket.h>
@@ -1492,6 +1493,40 @@ match_cleanup:
 			msg->rpl_send_flags.f|= SND_F_CON_CLOSE;
 			msg->rpl_send_flags.f|= SND_F_CON_CLOSE;
 			ret=1; /* continue processing */
 			ret=1; /* continue processing */
 			break;
 			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:
 		default:
 			LOG(L_CRIT, "BUG: do_action: unknown type %d\n", a->type);
 			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"
 INCLUDEFILE     "include_file"
 
 
+CFG_SELECT	"cfg_select"
+CFG_RESET	"cfg_reset"
+
 /*ACTION LVALUES*/
 /*ACTION LVALUES*/
 URIHOST			"uri:host"
 URIHOST			"uri:host"
 URIPORT			"uri:port"
 URIPORT			"uri:port"
@@ -635,6 +638,9 @@ SUBST       subst
 
 
 <INITIAL>{INCLUDEFILE}  { count(); BEGIN(INCLF); }
 <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>{URIHOST}	{ count(); yylval.strval=yytext; return URIHOST; }
 <INITIAL>{URIPORT}	{ count(); yylval.strval=yytext; return URIPORT; }
 <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)
  * 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 CASE
 %token DEFAULT
 %token DEFAULT
 %token WHILE
 %token WHILE
+%token CFG_SELECT
+%token CFG_RESET
 %token URIHOST
 %token URIHOST
 %token URIPORT
 %token URIPORT
 %token MAX_LEN
 %token MAX_LEN
@@ -1632,6 +1634,16 @@ cfg_var:
 	| cfg_var_id DOT cfg_var_id EQUAL error { 
 	| cfg_var_id DOT cfg_var_id EQUAL error { 
 		yyerror("number or string expected"); 
 		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:
 module_stm:
@@ -3261,6 +3273,19 @@ cmd:
 	| SET_RPL_CLOSE	{
 	| SET_RPL_CLOSE	{
 		$$=mk_action(SET_RPL_CLOSE_T, 0); set_cfg_pos($$);
 		$$=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,
 	| ID {mod_func_action = mk_action(MODULE0_T, 2, MODEXP_ST, NULL, NUMBER_ST,
 			0); } LPAREN func_params RPAREN	{
 			0); } LPAREN func_params RPAREN	{
 		mod_func_action->val[0].u.data =
 		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;
 	int	i, num, size, group_name_len;
 	cfg_mapping_t	*mapping = NULL;
 	cfg_mapping_t	*mapping = NULL;
+	cfg_group_t	*group;
 	int types;
 	int types;
 
 
 	/* check the number of the variables */
 	/* 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++) {
 	for (i=0, size=0; i<num; i++) {
 		mapping[i].def = &(def[i]);
 		mapping[i].def = &(def[i]);
 		mapping[i].name_len = strlen(def[i].name);
 		mapping[i].name_len = strlen(def[i].name);
+		mapping[i].pos = i;
 		/* record all the types for sanity checks */
 		/* record all the types for sanity checks */
 		types|=1 << CFG_VAR_MASK(def[i].type);
 		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);
 	group_name_len = strlen(group_name);
 	/* check for duplicates */
 	/* 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
 	/* The cfg variables are ready to use, let us set the handle
 	before passing the new definitions to the drivers.
 	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;
 	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 */
 /* returns the handle of a cfg group */
 void **cfg_get_handle(char *gname)
 void **cfg_get_handle(char *gname)
 {
 {
 	cfg_group_t	*group;
 	cfg_group_t	*group;
 
 
 	group = cfg_lookup_group(gname, strlen(gname));
 	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;
 	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 */
 /* declares a single variable with str type */
 int cfg_declare_str(char *group_name, char *var_name, char *val, char *descr);
 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 */
 /* returns the handle of a cfg group */
 void **cfg_get_handle(char *gname);
 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
 			/* dynamic groups are not ready, the callback
 			will be called later when the group is fixed-up */
 			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.s = group->name;
 			gname.len = group->name_len;
 			gname.len = group->name_len;
@@ -239,6 +239,66 @@ error:
 		} \
 		} \
 	} while(0)
 	} 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
 /* sets the value of a variable without the need of commit
  *
  *
  * return value:
  * return value:
@@ -246,17 +306,20 @@ error:
  *  -1: error
  *  -1: error
  *   1: variable has not been found
  *   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)
 			void *val, unsigned int val_type)
 {
 {
+	int		i;
 	cfg_group_t	*group;
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	cfg_mapping_t	*var;
 	void		*p, *v;
 	void		*p, *v;
 	cfg_block_t	*block = NULL;
 	cfg_block_t	*block = NULL;
 	str		s, s2;
 	str		s, s2;
 	char		*old_string = NULL;
 	char		*old_string = NULL;
-	char		**replaced = NULL;
+	void		**replaced = NULL;
 	cfg_child_cb_t	*child_cb = 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
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	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;
 		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 */
 	/* look-up the group and the variable */
 	if (cfg_lookup_var(group_name, var_name, &group, &var))
 	if (cfg_lookup_var(group_name, var_name, &group, &var))
 		return 1;
 		return 1;
@@ -276,6 +348,19 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		goto error0;
 		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 */
 	/* check whether we have to convert the type */
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 		goto error0;
 		goto error0;
@@ -294,7 +379,25 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		/* Call the fixup function.
 		/* Call the fixup function.
 		There is no need to set a temporary cfg handle,
 		There is no need to set a temporary cfg handle,
 		becaue a single variable is changed */
 		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,
 						group_name,
 						var_name,
 						var_name,
 						&v) < 0) {
 						&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,
 		/* get the name of the variable from the internal struct,
 		because var_name may be freed before the callback needs it */
 		because var_name may be freed before the callback needs it */
 		s.s = group->name;
 		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 */
 		while the new one is prepared */
 		CFG_WRITER_LOCK();
 		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) {
 		if (var->def->type & CFG_ATOMIC) {
 			/* atomic change is allowed, we can rewrite the value
 			/* atomic change is allowed, we can rewrite the value
 			directly in the global config */
 			directly in the global config */
-			p = (*cfg_global)->vars+group->offset+var->offset;
+			p = var_block + var->offset;
 
 
 		} else {
 		} else {
 			/* clone the memory block, and prepare the modification */
 			/* clone the memory block, and prepare the modification */
 			if (!(block = cfg_clone_global())) goto error;
 			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 {
 	} else {
 		/* we are allowed to rewrite the value on-the-fly
 		/* 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;
 		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 (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,
 			/* prepare the array of the replaced strings,
+			and replaced group instances,
 			they will be freed when the old block is freed */
 			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) {
 			if (!replaced) {
 				LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
 				LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
 				goto error;
 				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 */
 		/* replace the global config with the new one */
 		if (block) cfg_install_global(block, replaced, child_cb, child_cb);
 		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,
 			group_name->len, group_name->s,
 			var_name->len, var_name->s,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->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();
 	convert_val_cleanup();
 	return 0;
 	return 0;
@@ -430,7 +599,9 @@ int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
 error:
 error:
 	if (cfg_shmized) CFG_WRITER_UNLOCK();
 	if (cfg_shmized) CFG_WRITER_UNLOCK();
 	if (block) cfg_block_free(block);
 	if (block) cfg_block_free(block);
+	if (new_array) shm_free(new_array);
 	if (child_cb) cfg_child_cb_free(child_cb);
 	if (child_cb) cfg_child_cb_free(child_cb);
+	if (replaced) shm_free(replaced);
 
 
 error0:
 error0:
 	LOG(L_ERR, "ERROR: cfg_set_now(): failed to set the variable: %.*s.%.*s\n",
 	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 */
 /* 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 */
 /* 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 */
 /* 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
 /* 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: error
  *   1: variable has not been found
  *   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)
 			void *val, unsigned int val_type)
 {
 {
 	cfg_group_t	*group;
 	cfg_group_t	*group;
 	cfg_mapping_t	*var;
 	cfg_mapping_t	*var;
 	void		*v;
 	void		*v;
-	char		*temp_handle;
+	unsigned char	*temp_handle;
 	int		temp_handle_created;
 	int		temp_handle_created;
-	cfg_changed_var_t	*changed = NULL;
+	cfg_changed_var_t	*changed = NULL, **changed_p;
 	int		size;
 	int		size;
 	str		s;
 	str		s;
+	cfg_group_inst_t	*group_inst = NULL;
+	unsigned char		*var_block;
 
 
 	if (!cfg_shmized)
 	if (!cfg_shmized)
 		/* the cfg has not been shmized yet, there is no
 		/* the cfg has not been shmized yet, there is no
 		point in registering the change and committing it later */
 		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);
 					val, val_type);
 
 
 	if (!ctx) {
 	if (!ctx) {
@@ -524,6 +679,19 @@ int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
 		goto error0;
 		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 */
 	/* check whether we have to convert the type */
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 	if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
 		goto error0;
 		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,
 		Only the values within the group are applied,
 		other modifications are not visible to the callback.
 		other modifications are not visible to the callback.
 		The local config is the base. */
 		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) {
 		if (ctx->changed_first) {
-			temp_handle = (char *)pkg_malloc(group->size);
+			temp_handle = (unsigned char *)pkg_malloc(group->size);
 			if (!temp_handle) {
 			if (!temp_handle) {
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): "
 				LOG(L_ERR, "ERROR: cfg_set_delayed(): "
 					"not enough memory\n");
 					"not enough memory\n");
 				goto error;
 				goto error;
 			}
 			}
 			temp_handle_created = 1;
 			temp_handle_created = 1;
-			memcpy(temp_handle, *(group->handle), group->size);
+			memcpy(temp_handle, var_block, group->size);
 
 
 			/* apply the changes */
 			/* apply the changes */
 			for (	changed = ctx->changed_first;
 			for (	changed = ctx->changed_first;
 				changed;
 				changed;
 				changed = changed->next
 				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 {
 		} else {
 			/* there is not any change */
 			/* there is not any change */
-			temp_handle = *(group->handle);
+			temp_handle = var_block;
 			temp_handle_created = 0;
 			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);
 	memset(changed, 0, size);
 	changed->group = group;
 	changed->group = group;
 	changed->var = var;
 	changed->var = var;
+	if (group_id) {
+		changed->group_id = *group_id;
+		changed->group_id_set = 1;
+	}
 
 
 	switch (CFG_VAR_TYPE(var)) {
 	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);
 	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,
 			var_name->len, var_name->s,
 			((str *)val)->len, ((str *)val)->s,
 			((str *)val)->len, ((str *)val)->s,
 			ctx);
 			ctx);
+	if (group_id)
+		LOG(L_INFO, "INFO: cfg_set_delayed(): group id = %u "
+			"[context=%p]\n",
+			*group_id,
+			ctx);
 
 
 	convert_val_cleanup();
 	convert_val_cleanup();
 	return 0;
 	return 0;
@@ -683,21 +897,27 @@ error0:
 }
 }
 
 
 /* wrapper function for cfg_set_delayed */
 /* 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 */
 /* 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 */
 /* 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 */
 /* commits the previously prepared changes within the context */
@@ -705,14 +925,16 @@ int cfg_commit(cfg_ctx_t *ctx)
 {
 {
 	int	replaced_num = 0;
 	int	replaced_num = 0;
 	cfg_changed_var_t	*changed, *changed2;
 	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;
 	cfg_child_cb_t	*child_cb_first = NULL;
 	cfg_child_cb_t	*child_cb_first = NULL;
 	cfg_child_cb_t	*child_cb_last = NULL;
 	cfg_child_cb_t	*child_cb_last = NULL;
 	int	size;
 	int	size;
 	void	*p;
 	void	*p;
 	str	s, s2;
 	str	s, s2;
+	cfg_group_t	*group;
+	cfg_group_inst_t	*group_inst = NULL;
 
 
 	if (!ctx) {
 	if (!ctx) {
 		LOG(L_ERR, "ERROR: cfg_commit(): context is undefined\n");
 		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? */
 	/* is there any change? */
 	if (!ctx->changed_first) goto done;
 	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 = changed->next
 		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)
 		if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
 		|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 		|| (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
 			replaced_num++;
 			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.s = changed->group->name;
 			s.len = changed->group->name_len;
 			s.len = changed->group->name_len;
 			s2.s = changed->var->def->name;
 			s2.s = changed->var->def->name;
@@ -760,11 +993,11 @@ int cfg_commit(cfg_ctx_t *ctx)
 
 
 	if (replaced_num) {
 	if (replaced_num) {
 		/* allocate memory for the replaced string array */
 		/* 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) {
 		if (!replaced) {
 			LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
 			LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
-			goto error;
+			goto error0;
 		}
 		}
 		memset(replaced, 0 , size);
 		memset(replaced, 0 , size);
 	}
 	}
@@ -774,23 +1007,57 @@ int cfg_commit(cfg_ctx_t *ctx)
 	CFG_WRITER_LOCK();
 	CFG_WRITER_LOCK();
 
 
 	/* clone the memory block, and prepare the modification */
 	/* clone the memory block, and prepare the modification */
-	if (!(block = cfg_clone_global())) {
-		CFG_WRITER_UNLOCK();
+	if (!(block = cfg_clone_global()))
 		goto error;
 		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;
 	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 = changed->next
 		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;
 			replaced[replaced_num] = *(char **)p;
 			if (replaced[replaced_num])
 			if (replaced[replaced_num])
 				replaced_num++;
 				replaced_num++;
@@ -802,13 +1069,33 @@ int cfg_commit(cfg_ctx_t *ctx)
 		memcpy(	p,
 		memcpy(	p,
 			changed->new_val.vraw,
 			changed->new_val.vraw,
 			cfg_var_size(changed->var));
 			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 */
 	/* replace the global config with the new one */
 	cfg_install_global(block, replaced, child_cb_first, child_cb_last);
 	cfg_install_global(block, replaced, child_cb_first, child_cb_last);
 	CFG_WRITER_UNLOCK();
 	CFG_WRITER_UNLOCK();
 
 
-	/* free the changed list */	
+	/* free the changed list */
 	for (	changed = ctx->changed_first;
 	for (	changed = ctx->changed_first;
 		changed;
 		changed;
 		changed = changed2
 		changed = changed2
@@ -817,7 +1104,6 @@ int cfg_commit(cfg_ctx_t *ctx)
 		shm_free(changed);
 		shm_free(changed);
 	}
 	}
 	ctx->changed_first = NULL;
 	ctx->changed_first = NULL;
-	ctx->changed_last = NULL;
 
 
 done:
 done:
 	LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
 	LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
@@ -828,9 +1114,24 @@ done:
 	return 0;
 	return 0;
 
 
 error:
 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:
 error0:
+	CFG_CTX_UNLOCK(ctx);
 
 
 	if (child_cb_first) cfg_child_cb_free(child_cb_first);
 	if (child_cb_first) cfg_child_cb_free(child_cb_first);
 	if (replaced) shm_free(replaced);
 	if (replaced) shm_free(replaced);
@@ -872,7 +1173,6 @@ int cfg_rollback(cfg_ctx_t *ctx)
 		shm_free(changed);
 		shm_free(changed);
 	}
 	}
 	ctx->changed_first = NULL;
 	ctx->changed_first = NULL;
-	ctx->changed_last = NULL;
 
 
 	CFG_CTX_UNLOCK(ctx);
 	CFG_CTX_UNLOCK(ctx);
 
 
@@ -885,7 +1185,7 @@ int cfg_rollback(cfg_ctx_t *ctx)
  * -1 - error
  * -1 - error
  *  1 - variable exists, but it is not readable
  *  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)
 			void **val, unsigned int *val_type)
 {
 {
 	cfg_group_t	*group;
 	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;
 	void		*p;
 	static str	s;	/* we need the value even
 	static str	s;	/* we need the value even
 				after the function returns */
 				after the function returns */
+	cfg_group_inst_t	*group_inst;
 
 
 	/* verify the context even if we do not need it now
 	/* verify the context even if we do not need it now
 	to make sure that a cfg driver has called the function
 	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;
 		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)) {
 	switch (CFG_VAR_TYPE(var)) {
 	case CFG_VAR_INT:
 	case CFG_VAR_INT:
@@ -1005,13 +1323,18 @@ int cfg_diff_init(cfg_ctx_t *ctx,
 
 
 /* return the pending changes that have not been
 /* return the pending changes that have not been
  * committed yet
  * committed yet
+ * return value:
+ *	1: valid value is found
+ *	0: no more changed value found
+ *	-1: error occured 
  */
  */
 int cfg_diff_next(void **h,
 int cfg_diff_next(void **h,
-			str *gname, str *vname,
+			str *gname, unsigned int **gid, str *vname,
 			void **old_val, void **new_val,
 			void **old_val, void **new_val,
 			unsigned int *val_type)
 			unsigned int *val_type)
 {
 {
 	cfg_changed_var_t	*changed;
 	cfg_changed_var_t	*changed;
+	cfg_group_inst_t	*group_inst;
 	union cfg_var_value* pval;
 	union cfg_var_value* pval;
 	static str	old_s, new_s;	/* we need the value even
 	static str	old_s, new_s;	/* we need the value even
 					after the function returns */
 					after the function returns */
@@ -1021,14 +1344,32 @@ int cfg_diff_next(void **h,
 
 
 	gname->s = changed->group->name;
 	gname->s = changed->group->name;
 	gname->len = changed->group->name_len;
 	gname->len = changed->group->name_len;
+	*gid = (changed->group_id_set ? &changed->group_id : NULL);
 	vname->s = changed->var->def->name;
 	vname->s = changed->var->def->name;
 	vname->len = changed->var->name_len;
 	vname->len = changed->var->name_len;
 
 
 	/* use the module's handle to access the variable
 	/* use the module's handle to access the variable
 	It means that the variable is read from the local config
 	It means that the variable is read from the local config
 	after forking */
 	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)) {
 	switch (CFG_VAR_TYPE(changed->var)) {
 	case CFG_VAR_INT:
 	case CFG_VAR_INT:
@@ -1070,3 +1411,364 @@ void cfg_diff_release(cfg_ctx_t *ctx)
 
 
 	CFG_CTX_UNLOCK(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;
 	cfg_mapping_t	*var;
 	struct _cfg_changed_var	*next;
 	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 */
 	/* blob that contains the new value */
 	union cfg_var_value new_val; /* variable size */
 	union cfg_var_value new_val; /* variable size */
 } cfg_changed_var_t;
 } cfg_changed_var_t;
@@ -61,7 +64,6 @@ typedef struct _cfg_ctx {
 	/* variables that are already changed
 	/* variables that are already changed
 	but have not been committed yet */
 	but have not been committed yet */
 	cfg_changed_var_t	*changed_first;
 	cfg_changed_var_t	*changed_first;
-	cfg_changed_var_t	*changed_last;
 	/* lock protecting the linked-list of
 	/* lock protecting the linked-list of
 	changed variables */
 	changed variables */
 	gen_lock_t		lock;
 	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);
 void cfg_ctx_destroy(void);
 
 
 /*! \brief set the value of a variable without the need of explicit commit */
 /*! \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);
 			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 */
 /* 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);
 			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 */
 /*! \brief commits the previously prepared changes within the context */
 int cfg_commit(cfg_ctx_t *ctx);
 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);
 int cfg_rollback(cfg_ctx_t *ctx);
 
 
 /*! \brief returns the value of a variable */
 /*! \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);
 			void **val, unsigned int *val_type);
 
 
 /*! \brief returns the description of a variable */
 /*! \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
 /*! \brief return the pending changes that have not been
  * committed yet
  * committed yet
+ * return value:
+ *	1: valid value is found
+ *	0: no more changed value found
+ *	-1: error occured
+ *
  *
  *
  * can be used as follows:
  * can be used as follows:
  *
  *
  * void *handle;
  * void *handle;
  * if (cfg_diff_init(ctx, &handle)) return -1
  * 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
  *			&old_val, &new_val
- *			&val_type)
+ *			&val_type)) > 0
  * ) {
  * ) {
  *		...
  *		...
  * }
  * }
  * cfg_diff_release(ctx);
  * cfg_diff_release(ctx);
+ * if (err) {
+ *	error occured, the changes cannot be retrieved
+ *	...
+ * }
  */
  */
 int cfg_diff_next(void **h,
 int cfg_diff_next(void **h,
-			str *gname, str *vname,
+			str *gname, unsigned int **gid, str *vname,
 			void **old_val, void **new_val,
 			void **old_val, void **new_val,
 			unsigned int *val_type);
 			unsigned int *val_type);
 
 
 /*! \brief destroy the handle of cfg_diff_next() */
 /*! \brief destroy the handle of cfg_diff_next() */
 void cfg_diff_release(cfg_ctx_t *ctx);
 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 */
 #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 */
 	/* the group may have been already declared */
 	group = cfg_lookup_group(gname, gname_len);
 	group = cfg_lookup_group(gname, gname_len);
 	if (group) {
 	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 */
 			/* the group has been already declared by a module or by the core */
 			LOG(L_ERR, "ERROR: new_cfg_script_var(): "
 			LOG(L_ERR, "ERROR: new_cfg_script_var(): "
 				"configuration group has been already declared: %s\n",
 				"configuration group has been already declared: %s\n",
 				gname);
 				gname);
 			return NULL;
 			return NULL;
 		}
 		}
-		/* the dynamic group is found */
+		/* the dynamic or empty group is found */
 		/* verify that the variable does not exist */
 		/* verify that the variable does not exist */
 		for (	var = (cfg_script_var_t *)group->vars;
 		for (	var = (cfg_script_var_t *)group->vars;
 			var;
 			var;
@@ -76,6 +76,8 @@ cfg_script_var_t *new_cfg_script_var(char *gname, char *vname, unsigned int type
 				return NULL;
 				return NULL;
 			}
 			}
 		}
 		}
+		if (group->dynamic == CFG_GROUP_UNKNOWN)
+			group->dynamic = CFG_GROUP_DYNAMIC;
 
 
 	} else {
 	} else {
 		/* create a new group with NULL values, we will fix it later,
 		/* 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 */);
 					NULL /* vars */, 0 /* size */, NULL /* handle */);
 					
 					
 		if (!group) goto error;
 		if (!group) goto error;
-		group->dynamic = 1;
+		group->dynamic = CFG_GROUP_DYNAMIC;
 	}
 	}
 
 
 	switch (type) {
 	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");
 		LOG(L_ERR, "ERROR: new_cfg_script_var(): unsupported variable type\n");
 		return NULL;
 		return NULL;
 	}
 	}
+
 	group->num++;
 	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));
 	var = (cfg_script_var_t *)pkg_malloc(sizeof(cfg_script_var_t));
 	if (!var) goto error;
 	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].def = &(def[i]);
 		mapping[i].name_len = script_var->name_len;
 		mapping[i].name_len = script_var->name_len;
+		mapping[i].pos = i;
 
 
 		switch (script_var->type) {
 		switch (script_var->type) {
 		case CFG_VAR_INT:
 		case CFG_VAR_INT:

+ 1 - 0
cfg/cfg_script.h

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

+ 507 - 12
cfg/cfg_struct.c

@@ -32,6 +32,7 @@
 #include "../mem/shm_mem.h"
 #include "../mem/shm_mem.h"
 #include "../ut.h"
 #include "../ut.h"
 #include "../locking.h"
 #include "../locking.h"
+#include "../bit_scan.h"
 #include "cfg_ctx.h"
 #include "cfg_ctx.h"
 #include "cfg_script.h"
 #include "cfg_script.h"
 #include "cfg_select.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.
 cfg_block_t	*cfg_local = NULL;	/* per-process pointer to the active cfg block.
 					Updated only when the child process
 					Updated only when the child process
 					finishes working on the SIP message */
 					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_global_lock = 0;	/* protects *cfg_global */
 gen_lock_t	*cfg_writer_lock = 0;	/* This lock makes sure that two processes do not
 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.
 					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 */
 						callback list */
 cfg_child_cb_t	**cfg_child_cb_last = NULL;	/* last item of the above 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 */	
 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 */
 /* creates a new cfg group, and adds it to the linked list */
 cfg_group_t *cfg_new_group(char *name, int name_len,
 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;
 		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);
 	group = (cfg_group_t *)pkg_malloc(sizeof(cfg_group_t)+name_len-1);
 	if (!group) {
 	if (!group) {
 		LOG(L_ERR, "ERROR: cfg_new_group(): not enough memory\n");
 		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;
 	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
 /* clones a string to shared memory
  * (src and dst can be the same)
  * (src and dst can be the same)
  */
  */
@@ -162,19 +189,37 @@ int cfg_shmize(void)
 	if (!cfg_group) return 0;
 	if (!cfg_group) return 0;
 
 
 	/* Let us allocate one memory block that
 	/* 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;
 	for (	size=0, group = cfg_group;
 		group;
 		group;
 		group=group->next
 		group=group->next
 	) {
 	) {
 		size = ROUND_POINTER(size);
 		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;
 		size += group->size;
 	}
 	}
 
 
 	block = (cfg_block_t*)shm_malloc(sizeof(cfg_block_t)+size-1);
 	block = (cfg_block_t*)shm_malloc(sizeof(cfg_block_t)+size-1);
 	if (!block) {
 	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;
 		goto error;
 	}
 	}
 	memset(block, 0, sizeof(cfg_block_t)+size-1);
 	memset(block, 0, sizeof(cfg_block_t)+size-1);
@@ -185,29 +230,40 @@ int cfg_shmize(void)
 		group;
 		group;
 		group=group->next
 		group=group->next
 	) {
 	) {
-		if (group->dynamic == 0) {
+		if (group->dynamic == CFG_GROUP_STATIC) {
 			/* clone the strings to shm mem */
 			/* clone the strings to shm mem */
 			if (cfg_shmize_strings(group)) goto error;
 			if (cfg_shmize_strings(group)) goto error;
 
 
 			/* copy the values to the new block */
 			/* 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,
 			/* The group was declared with NULL values,
 			 * we have to fix it up.
 			 * we have to fix it up.
 			 * The fixup function takes care about the values,
 			 * The fixup function takes care about the values,
 			 * it fills up the block */
 			 * 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.
 			/* Notify the drivers about the new config definition.
 			 * Temporary set the group handle so that the drivers have a chance to
 			 * Temporary set the group handle so that the drivers have a chance to
 			 * overwrite the default values. The handle must be reset after this
 			 * overwrite the default values. The handle must be reset after this
 			 * because the main process does not have a local configuration. */
 			 * 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,
 			cfg_notify_drivers(group->name, group->name_len,
 					group->mapping->def);
 					group->mapping->def);
 			*(group->handle) = NULL;
 			*(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 */
 	/* try to fixup the selects that failed to be fixed-up previously */
 	if (cfg_fixup_selects()) goto error;
 	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)) &&
 				(CFG_VAR_TYPE(&mapping[i]) == CFG_VAR_STR)) &&
 					mapping[i].flag & cfg_var_shmized) {
 					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 (old_string) shm_free(old_string);
 				}
 				}
 
 
-		if (group->dynamic) {
+		if (group->dynamic == CFG_GROUP_DYNAMIC) {
 			/* the group was dynamically allocated */
 			/* the group was dynamically allocated */
 			cfg_script_destroy(group);
 			cfg_script_destroy(group);
 		} else {
 		} else {
@@ -255,6 +311,8 @@ static void cfg_destory_groups(unsigned char *block)
 			pointers are just set to static variables */
 			pointers are just set to static variables */
 			if (mapping) pkg_free(mapping);
 			if (mapping) pkg_free(mapping);
 		}
 		}
+		/* Delete the additional variable list */
+		del_add_var_list(group);
 
 
 		group2 = group->next;
 		group2 = group->next;
 		pkg_free(group);
 		pkg_free(group);
@@ -538,6 +596,29 @@ int cfg_lookup_var(str *gname, str *vname,
 	return -1;
 	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
 /* clones the global config block
  * WARNING: unsafe, cfg_writer_lock or cfg_global_lock must be held!
  * 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;
 	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
 /* append new callbacks to the end of the child callback list
  *
  *
  * WARNING: the function is unsafe, either hold CFG_LOCK(),
  * 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
  * cb_first and cb_last define a linked list of per-child process
  * callbacks. This list is added to the global linked list.
  * 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_child_cb_t *cb_first, cfg_child_cb_t *cb_last)
 {
 {
 	cfg_block_t* old_cfg;
 	cfg_block_t* old_cfg;
@@ -657,3 +862,293 @@ void cfg_child_cb_free(cfg_child_cb_t *child_cb_first)
 		shm_free(cb);
 		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 "../mem/shm_mem.h"
 #include "../locking.h"
 #include "../locking.h"
 #include "../compiler_opt.h"
 #include "../compiler_opt.h"
+#include "../bit_test.h"
 #include "cfg.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 */
 /*! \brief indicates that the variable has been already shmized */
 #define cfg_var_shmized	1U
 #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 */
 /*! \brief structure used for variable - pointer mapping */
 typedef struct _cfg_mapping {
 typedef struct _cfg_mapping {
 	cfg_def_t	*def;		/*!< one item of the cfg structure definition */
 	cfg_def_t	*def;		/*!< one item of the cfg structure definition */
 	int		name_len;	/*!< length of def->name */
 	int		name_len;	/*!< length of def->name */
 
 
 	/* additional information about the cfg variable */
 	/* 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 */
 	int		offset; /*!< offest within the memory block */
 	unsigned int	flag;	/*!< flag indicating the state of the variable */
 	unsigned int	flag;	/*!< flag indicating the state of the variable */
 } cfg_mapping_t;
 } 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 */
 /*! \brief linked list of registered groups */
 typedef struct _cfg_group {
 typedef struct _cfg_group {
 	int		num;		/*!< number of variables within the 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
 	char		*vars;		/*!< pointer to the memory block where the values
 					are stored -- used only before the config is
 					are stored -- used only before the config is
 					shmized. */
 					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
 	int		size;		/*!< size of the memory block that has to be
 					allocated to store the values */
 					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
 	void		**handle;	/*!< per-process handle that can be used
 					by the modules to access the variables.
 					by the modules to access the variables.
 					It is registered when the group is created,
 					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
 	unsigned char	dynamic;	/*!< indicates whether the variables within the group
 					are dynamically	allocated or not */
 					are dynamically	allocated or not */
 	struct _cfg_group	*next;
 	struct _cfg_group	*next;
-	int		name_len;	
+	int		name_len;
 	char		name[1];
 	char		name[1];
 } cfg_group_t;
 } 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 */
 /*! \brief single memoy block that contains all the cfg values */
 typedef struct _cfg_block {
 typedef struct _cfg_block {
 	atomic_t	refcnt;		/*!< reference counter,
 	atomic_t	refcnt;		/*!< reference counter,
 					the block is automatically deleted
 					the block is automatically deleted
 					when it reaches 0 */
 					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
 					together with the block. The content depends
 					on the block that replaces this one */
 					on the block that replaces this one */
 	unsigned char	vars[1];	/*!< blob that contains the values */
 	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_group_t	*cfg_group;
 extern cfg_block_t	**cfg_global;
 extern cfg_block_t	**cfg_global;
 extern cfg_block_t	*cfg_local;
 extern cfg_block_t	*cfg_local;
+extern int		cfg_block_size;
 extern gen_lock_t	*cfg_global_lock;
 extern gen_lock_t	*cfg_global_lock;
 extern gen_lock_t	*cfg_writer_lock;
 extern gen_lock_t	*cfg_writer_lock;
 extern int		cfg_shmized;
 extern int		cfg_shmized;
 extern cfg_child_cb_t	**cfg_child_cb_first;
 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_last;
 extern cfg_child_cb_t	*cfg_child_cb;
 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
 /* magic value for cfg_child_cb for processes that do not want to
    execute per-child callbacks */
    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_VAR_TYPE(var)	CFG_VAR_MASK((var)->def->type)
 #define CFG_INPUT_TYPE(var)	CFG_INPUT_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 */
 /* initiate the cfg framework */
 int sr_cfg_init(void);
 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,
 		int num, cfg_mapping_t *mapping,
 		char *vars, int size, void **handle);
 		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 */
 /* copy the variables to shm mem */
 int cfg_shmize(void);
 int cfg_shmize(void);
 
 
@@ -190,6 +279,16 @@ static inline void cfg_block_free(cfg_block_t *block)
 	shm_free(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
 /* lock and unlock the global cfg block -- used only at the
  * very last step when the block is replaced */
  * very last step when the block is replaced */
 #define CFG_LOCK()	lock_get(cfg_global_lock);
 #define CFG_LOCK()	lock_get(cfg_global_lock);
@@ -242,7 +341,7 @@ static inline void cfg_update_local(int no_cbs)
 		group;
 		group;
 		group = group->next
 		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))
 	if (unlikely(cfg_child_cb==CFG_NO_CHILD_CBS || no_cbs))
 		return;
 		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
 /* sets the local cfg block to the active block
  * 
  * 
  * If your module forks a new process that implements
  * 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() \
 #define cfg_update() \
 	do { \
 	do { \
+		if (unlikely(cfg_ginst_count)) \
+			cfg_reset_handles(); \
 		if (unlikely(cfg_local != *cfg_global)) \
 		if (unlikely(cfg_local != *cfg_global)) \
 			cfg_update_local(0); \
 			cfg_update_local(0); \
 	} while(0)
 	} while(0)
@@ -297,6 +421,16 @@ static inline void cfg_update_local(int no_cbs)
 			cfg_update_local(1); \
 			cfg_update_local(1); \
 	} while(0)
 	} 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 */
 /* searches a group by name */
 cfg_group_t *cfg_lookup_group(char *name, int len);
 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,
 int cfg_lookup_var(str *gname, str *vname,
 			cfg_group_t **group, cfg_mapping_t **var);
 			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);
 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 */
 /* clones a string to shared memory */
 int cfg_clone_str(str *src, str *dst);
 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
 /* append new callbacks to the end of the child callback list
  *
  *
  * WARNING: the function is unsafe, either hold CFG_LOCK(),
  * 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
  * cb_first and cb_last define a linked list of per-child process
  * callbacks. This list is added to the global linked list.
  * 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_child_cb_t *cb_first, cfg_child_cb_t *cb_last);
 
 
 /* creates a structure for a per-child process callback */
 /* 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 */
 /* free the memory allocated for a child cb list */
 void cfg_child_cb_free(cfg_child_cb_t *child_cb_first);
 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 */
 #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
 the framework can keep track of the changes (per driver) until they
 are committed or rolled-back.
 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
 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
 				of the cfg framework. By default this callback is
 				called by all the child processes separately,
 				called by all the child processes separately,
 				this can be changed with this flag.
 				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)
 - minimum value for integers (optional)
 - maximum value for integers (optional)
 - maximum value for integers (optional)
@@ -323,10 +334,11 @@ void		*h;
 str		gname, vname;
 str		gname, vname;
 void		*old_val, *new_val;
 void		*old_val, *new_val;
 unsigned int	val_type;
 unsigned int	val_type;
+unsigned int	*group_id;
 
 
 if (cfg_diff_init(ctx, &h)) return -1;
 if (cfg_diff_init(ctx, &h)) return -1;
 while(cfg_diff_next(&h,
 while(cfg_diff_next(&h,
-		&gname, &vname,
+		&gname, &group_id, &vname,
 		&old_val, &new_val,
 		&old_val, &new_val,
 		&val_type)
 		&val_type)
 ) {
 ) {
@@ -334,6 +346,11 @@ while(cfg_diff_next(&h,
 }
 }
 cfg_diff_release(ctx);
 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
 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:
 The values can be accessed via select calls:
 
 
 @cfg_get.<group_name>.<var_name>
 @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 */
 			/* read and set cfg var */
 			switch (rec->fld[0].type) {
 			switch (rec->fld[0].type) {
 				case DB_STR:
 				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;
 					break;
 				case DB_CSTR:					
 				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;
 					break;
 				case DB_INT:
 				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;
 					break;
 				default:
 				default:
 					ERR(MODULE_NAME": unexpected field type (%d), table:'%s', field:'%s'\n", 
 					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]>
    <[email protected]>
 
 
-   Copyright © 2007 iptelorg GmbH
+   Copyright © 2007 iptelorg GmbH
      __________________________________________________________________
      __________________________________________________________________
 
 
    1.1. Overview
    1.1. Overview
@@ -20,27 +20,53 @@ Miklos Tirpak
 1.2. RPC Interface
 1.2. RPC Interface
 
 
    The module implements the following RPC interface commands:
    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
        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
        variable, but does not commit the new value yet. The function
        accepts three parameters: group name, variable name, integer value.
        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
        variable, but does not commit the new value yet. The function
        accepts three parameters: group name, variable name, string value.
        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.
        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.
        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.
        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;
 	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] = {
 static const char* rpc_set_now_doc[2] = {
         "Set the value of a configuration variable and commit the change immediately",
         "Set the value of a configuration variable and commit the change immediately",
         0
         0
@@ -58,11 +92,17 @@ static void rpc_set_now_int(rpc_t* rpc, void* c)
 {
 {
 	str	group, var;
 	str	group, var;
 	int	i;
 	int	i;
+	unsigned int	*group_id;
 
 
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 		return;
 		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");
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 		return;
 	}
 	}
@@ -72,11 +112,45 @@ static void rpc_set_now_string(rpc_t* rpc, void* c)
 {
 {
 	str	group, var;
 	str	group, var;
 	char	*ch;
 	char	*ch;
+	unsigned int	*group_id;
 
 
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 		return;
 		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");
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 		return;
 	}
 	}
@@ -91,11 +165,17 @@ static void rpc_set_delayed_int(rpc_t* rpc, void* c)
 {
 {
 	str	group, var;
 	str	group, var;
 	int	i;
 	int	i;
+	unsigned int	*group_id;
 
 
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 	if (rpc->scan(c, "SSd", &group, &var, &i) < 3)
 		return;
 		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");
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 		return;
 	}
 	}
@@ -105,11 +185,45 @@ static void rpc_set_delayed_string(rpc_t* rpc, void* c)
 {
 {
 	str	group, var;
 	str	group, var;
 	char	*ch;
 	char	*ch;
+	unsigned int	*group_id;
 
 
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 	if (rpc->scan(c, "SSs", &group, &var, &ch) < 3)
 		return;
 		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");
 		rpc->fault(c, 400, "Failed to set the variable");
 		return;
 		return;
 	}
 	}
@@ -152,11 +266,17 @@ static void rpc_get(rpc_t* rpc, void* c)
 	void	*val;
 	void	*val;
 	unsigned int	val_type;
 	unsigned int	val_type;
 	int	ret;
 	int	ret;
+	unsigned int	*group_id;
 
 
 	if (rpc->scan(c, "SS", &group, &var) < 2)
 	if (rpc->scan(c, "SS", &group, &var) < 2)
 		return;
 		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);
 			&val, &val_type);
 	if (ret < 0) {
 	if (ret < 0) {
 		rpc->fault(c, 400, "Failed to get the variable");
 		rpc->fault(c, 400, "Failed to get the variable");
@@ -233,11 +353,21 @@ static void rpc_list(rpc_t* rpc, void* c)
 	str		gname;
 	str		gname;
 	cfg_def_t	*def;
 	cfg_def_t	*def;
 	int		i;
 	int		i;
+	str		group;
+
+	if (rpc->scan(c, "*S", &group) < 1) {
+		group.s = NULL;
+		group.len = 0;
+	}
 
 
 	cfg_get_group_init(&h);
 	cfg_get_group_init(&h);
 	while(cfg_get_group_next(&h, &gname, &def))
 	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] = {
 static const char* rpc_diff_doc[2] = {
@@ -249,24 +379,32 @@ static void rpc_diff(rpc_t* rpc, void* c)
 {
 {
 	void		*h;
 	void		*h;
 	str		gname, vname;
 	str		gname, vname;
+	unsigned int	*gid;
 	void		*old_val, *new_val;
 	void		*old_val, *new_val;
 	unsigned int	val_type;
 	unsigned int	val_type;
 	void		*rpc_handle;
 	void		*rpc_handle;
+	int		err;
 
 
 
 
 	if (cfg_diff_init(ctx, &h)) {
 	if (cfg_diff_init(ctx, &h)) {
 		rpc->fault(c, 400, "Failed to get the changes");
 		rpc->fault(c, 400, "Failed to get the changes");
 		return;
 		return;
 	}
 	}
-	while(cfg_diff_next(&h,
-			&gname, &vname,
+	while((err = cfg_diff_next(&h,
+			&gname, &gid, &vname,
 			&old_val, &new_val,
 			&old_val, &new_val,
-			&val_type)
+			&val_type)) > 0
 	) {
 	) {
 		rpc->add(c, "{", &rpc_handle);
 		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) {
 		switch (val_type) {
 		case CFG_VAR_INT:
 		case CFG_VAR_INT:
@@ -294,11 +432,63 @@ static void rpc_diff(rpc_t* rpc, void* c)
 		}
 		}
 	}
 	}
 	cfg_diff_release(ctx);
 	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[] = {
 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_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_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_int",	rpc_set_delayed_int,	rpc_set_delayed_doc,	0},
 	{"cfg.set_delayed_string",	rpc_set_delayed_string,	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},
 	{"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.help",		rpc_help,		rpc_help_doc,		0},
 	{"cfg.list",		rpc_list,		rpc_list_doc,		0},
 	{"cfg.list",		rpc_list,		rpc_list_doc,		0},
 	{"cfg.diff",		rpc_diff,		rpc_diff_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}
 	{0, 0, 0, 0}
 };
 };
 
 

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

@@ -12,76 +12,118 @@
     <itemizedlist>
     <itemizedlist>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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.
 		a configuration variable and commit the change immediately.
 		The function accepts three parameters: group name, variable
 		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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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.
 		a configuration variable and commit the change immediately.
 		The function accepts three parameters: group name, variable
 		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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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.
 		a configuration variable, but does not commit the new value yet.
 		The function accepts three parameters: group name, variable
 		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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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.
 		a configuration variable, but does not commit the new value yet.
 		The function accepts three parameters: group name, variable
 		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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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
 		prepared configuration changes. The function does not have
 		any parameters.
 		any parameters.
 	    </para>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <para>
-		<emphasis>cfg_rpc.rollback</emphasis> - Drop the prepared
+		<emphasis>cfg.rollback</emphasis> - Drop the prepared
 		configuration changes. The function does not have any
 		configuration changes. The function does not have any
 		parameters.
 		parameters.
 	    </para>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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:
 		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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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:
 		a configuration variable. The function accepts two parameters:
 		group name, variable name.
 		group name, variable name.
 	    </para>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <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>
 	    </para>
 	</listitem>
 	</listitem>
 	<listitem>
 	<listitem>
 	    <para>
 	    <para>
-		<emphasis>cfg_rpc.diff</emphasis> - List the pending
+		<emphasis>cfg.diff</emphasis> - List the pending
 		configuration changes that have not been committed yet.
 		configuration changes that have not been committed yet.
 		The function does not have any parameters.
 		The function does not have any parameters.
 	    </para>
 	    </para>
 	</listitem>
 	</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>
     </itemizedlist>
 </section>
 </section>

+ 99 - 25
modules/ctl/binrpc_run.c

@@ -97,6 +97,8 @@ struct binrpc_ctx{
 	char* method;
 	char* method;
 	struct binrpc_gc_block* gc; /**< garbage collection */
 	struct binrpc_gc_block* gc; /**< garbage collection */
 	int replied;
 	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);
 		pkg_free(ctx->out.pkt.body);
 		ctx->out.pkt.body=0;
 		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);
 	binrpc_gc_collect(ctx);
 }
 }
 
 
@@ -395,32 +401,23 @@ inline void destroy_binrpc_ctx(struct binrpc_ctx* ctx)
 
 
 #define MAX_FAULT_LEN 256
 #define MAX_FAULT_LEN 256
 #define FAULT_START_BUF (3 /* maxint*/+2/*max str header*/)
 #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 fault_start[FAULT_START_BUF];
 	static unsigned char hdr[BINRPC_MAX_HDR_SIZE];
 	static unsigned char hdr[BINRPC_MAX_HDR_SIZE];
 	struct iovec v[3];
 	struct iovec v[3];
 	struct binrpc_pkt body;
 	struct binrpc_pkt body;
 	int b_len;
 	int b_len;
-	va_list ap;
-	int len;
 	int hdr_len;
 	int hdr_len;
 	int err;
 	int err;
-	
+
 	if (ctx->replied){
 	if (ctx->replied){
 		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
 		LOG(L_ERR, "ERROR: binrpc: rpc_send: rpc method %s tried to reply"
 					" more then once\n", ctx->method?ctx->method:"");
 					" more then once\n", ctx->method?ctx->method:"");
 		return;
 		return;
 	}
 	}
 	err=0;
 	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);
 	err=binrpc_init_pkt(&body, fault_start, FAULT_START_BUF);
 	if (err<0){
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_init_pkt error\n");
 		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 */
 	/* adding a fault "manually" to avoid extra memcpys */
 	err=binrpc_addint(&body, code);
 	err=binrpc_addint(&body, code);
 	if (err<0){
 	if (err<0){
-		LOG(L_ERR, "ERROR: rpc_fault: addint error\n");
+		LOG(L_ERR, "ERROR: _rpc_fault: addint error\n");
 		goto error;
 		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){
 	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;
 		goto error;
 	}
 	}
 	/*
 	/*
-	err=binrpc_addfault(&body, code, buf, len); 
+	err=binrpc_addfault(&body, code, phrase, phrase_len);
 	if (err<0){
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_addfault error\n");
 		LOG(L_ERR, "ERROR: binrpc_addfault error\n");
 		goto error;
 		goto error;
 	}*/
 	}*/
 	b_len=binrpc_pkt_len(&body);
 	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);
 								ctx->in.ctx.cookie, hdr, BINRPC_MAX_HDR_SIZE);
 	if (err<0){
 	if (err<0){
 		LOG(L_ERR, "ERROR: binrpc_build_hdr error\n");
 		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[0].iov_len=hdr_len;
 	v[1].iov_base=body.body;
 	v[1].iov_base=body.body;
 	v[1].iov_len=b_len;
 	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=sock_send_v(ctx->send_h, v, 3))<0){
 		if (err==-2){
 		if (err==-2){
-			LOG(L_ERR, "ERROR: binrpc_fault: send failed: "
+			LOG(L_ERR, "ERROR: _rpc_fault: send failed: "
 					"datagram too big\n");
 					"datagram too big\n");
 			return;
 			return;
 		}
 		}
-		LOG(L_ERR, "ERROR: binrpc_fault: send failed\n");
+		LOG(L_ERR, "ERROR: _rpc_fault: send failed\n");
 		return;
 		return;
 	}
 	}
 	ctx->replied=1;
 	ctx->replied=1;
 	return;
 	return;
 error:
 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);
 			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 */
 /* build the reply from the current body */
 static int rpc_send(struct binrpc_ctx* ctx)
 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;
 	f_ctx.method=val.u.strval.s;
 	rpc_e->function(&binrpc_callbacks, &f_ctx);
 	rpc_e->function(&binrpc_callbacks, &f_ctx);
 	if (f_ctx.replied==0){
 	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
 		/* to get an error reply if the rpc handlers hasn't replied
 		 *  uncomment the following code:
 		 *  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");
 			rpc_fault(&f_ctx, 500, "internal server error: no reply");
 			LOG(L_ERR, "ERROR: rpc method %s hasn't replied\n",
 			LOG(L_ERR, "ERROR: rpc method %s hasn't replied\n",
 					val.u.strval.s);
 					val.u.strval.s);
-		}else  */
+		 */
+		} else {
 			rpc_send(&f_ctx);
 			rpc_send(&f_ctx);
+		}
 	}
 	}
 end:
 end:
 	*bytes_needed=0; /* full read */
 	*bytes_needed=0; /* full read */
@@ -749,6 +818,9 @@ static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...)
 	double d;
 	double d;
 	str* s;
 	str* s;
 	
 	
+	/* clear the previously saved error code */
+	rpc_fault_reset(ctx);
+
 	va_start(ap, fmt);
 	va_start(ap, fmt);
 	orig_fmt=fmt;
 	orig_fmt=fmt;
 	nofault = 0;
 	nofault = 0;
@@ -823,8 +895,10 @@ static int rpc_scan(struct binrpc_ctx* ctx, char* fmt, ...)
 	va_end(ap);
 	va_end(ap);
 	return (int)(fmt-orig_fmt)-modifiers;
 	return (int)(fmt-orig_fmt)-modifiers;
 error_read:
 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)))
 	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),
 						" %s", ctx->in.record_no, rpc_type_name(v.type),
 						 binrpc_error(err));
 						 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.s = "low_mem_threshold1";
 		s.len = strlen(s.s);
 		s.len = strlen(s.s);
 		if (low_mem_threshold1 != cfg_get(tls, tls_cfg, low_mem_threshold1) &&
 		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",
 			ERR("failed to set tls.low_mem_threshold1 to %d\n",
 					low_mem_threshold1);
 					low_mem_threshold1);
 			return -1;
 			return -1;
@@ -613,7 +613,7 @@ int init_tls_h(void)
 		s.s = "low_mem_threshold2";
 		s.s = "low_mem_threshold2";
 		s.len = strlen(s.s);
 		s.len = strlen(s.s);
 		if (low_mem_threshold2 != cfg_get(tls, tls_cfg, low_mem_threshold2) &&
 		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",
 			ERR("failed to set tls.low_mem_threshold1 to %d\n",
 					low_mem_threshold2);
 					low_mem_threshold2);
 			return -1;
 			return -1;

+ 14 - 0
modules/xmlrpc/xmlrpc.c

@@ -642,6 +642,15 @@ static int init_xmlrpc_reply(struct xmlrpc_reply* reply)
 	return 0;
 	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 */
 /* 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;
 	va_list ap;
 
 
 	reply = &ctx->reply;
 	reply = &ctx->reply;
+	/* clear the previously saved error code */
+	clear_xmlrpc_reply(reply);
+
 	fmt_len = strlen(fmt);
 	fmt_len = strlen(fmt);
 	va_start(ap, fmt);
 	va_start(ap, fmt);
 	modifiers=0;
 	modifiers=0;
@@ -1776,6 +1788,8 @@ static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
 	while(*fmt) {
 	while(*fmt) {
 		member_name = va_arg(ap, char*);
 		member_name = va_arg(ap, char*);
 		reply = s->reply;
 		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);
 		ret = find_member(&value, s->doc, s->struct_in, reply, member_name);
 		if (ret != 0) goto error;
 		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));
 			return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
 		set = 1;
 		set = 1;
 	} else {
 	} 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)
 					&val_type)!=0)
 			return init_mi_tree( 500, MI_SSTR(MI_INTERNAL_ERR));
 			return init_mi_tree( 500, MI_SSTR(MI_INTERNAL_ERR));
 		new_debug = (int)(long)vval;
 		new_debug = (int)(long)vval;
@@ -346,7 +346,7 @@ static struct mi_root *mi_debug(struct mi_root *cmd, void *param)
 	}
 	}
 
 
 	if(set==1) {
 	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);
 				(void *)(long)new_debug, CFG_VAR_INT);
 	}
 	}
 
 

+ 55 - 0
route.c

@@ -94,6 +94,7 @@
 #include "ut.h"
 #include "ut.h"
 #include "rvalue.h"
 #include "rvalue.h"
 #include "switch.h"
 #include "switch.h"
+#include "cfg/cfg_struct.h"
 
 
 #define RT_HASH_SIZE	8 /* route names hash */
 #define RT_HASH_SIZE	8 /* route names hash */
 
 
@@ -1149,6 +1150,60 @@ int fix_actions(struct action* a)
 					goto error;
 					goto error;
 				}
 				}
 				break;
 				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:
 			default:
 				/* no fixup required for the rest */
 				/* no fixup required for the rest */
 				break;
 				break;

+ 5 - 2
route_struct.h

@@ -115,7 +115,9 @@ enum action_type{
 		SET_FWD_NO_CONNECT_T,
 		SET_FWD_NO_CONNECT_T,
 		SET_RPL_NO_CONNECT_T,
 		SET_RPL_NO_CONNECT_T,
 		SET_FWD_CLOSE_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
 /* parameter types for actions or types for expression right operands
    (WARNING right operands only, not working for left operands) */
    (WARNING right operands only, not working for left operands) */
@@ -130,7 +132,8 @@ enum _operand_subtype{
 		SELECT_UNFIXED_ST,
 		SELECT_UNFIXED_ST,
 		STRING_RVE_ST /* RVE converted to a string (fparam hack) */,
 		STRING_RVE_ST /* RVE converted to a string (fparam hack) */,
 		RVE_FREE_FIXUP_ST /* (str)RVE fixed up by a reversable fixup */,
 		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;
 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
 #endif
 			UNLOCK_TIMER_LIST(); /* acts also as write barrier */ 
 			UNLOCK_TIMER_LIST(); /* acts also as write barrier */ 
 				ret=tl->f(t, tl, tl->data);
 				ret=tl->f(t, tl, tl->data);
+				/* reset the configuration group handles */
+				cfg_reset_all();
 				if (ret==0){
 				if (ret==0){
 					UNSET_RUNNING();
 					UNSET_RUNNING();
 					LOCK_TIMER_LIST();
 					LOCK_TIMER_LIST();
@@ -1147,6 +1149,8 @@ void slow_timer_main()
 				SET_RUNNING_SLOW(tl);
 				SET_RUNNING_SLOW(tl);
 				UNLOCK_SLOW_TIMER_LIST();
 				UNLOCK_SLOW_TIMER_LIST();
 					ret=tl->f(*ticks, tl, tl->data);
 					ret=tl->f(*ticks, tl, tl->data);
+					/* reset the configuration group handles */
+					cfg_reset_all();
 					if (ret==0){
 					if (ret==0){
 						/* one shot */
 						/* one shot */
 						UNSET_RUNNING_SLOW();
 						UNSET_RUNNING_SLOW();