# $Id: cfg.txt,v 1.3 2007/12/12 16:41:51 tirpi Exp $
#
# History:
# --------
# 2007-12-06:	Created by Miklos

SER Configuration Framework

1. Overview
===============================================================================

The configuration framework can be used by SER core and by the modules,
to get and set internal variables on-the-fly, and eliminate SER restarts
whenever it is possible.

The core and the modules can declare configuration variables, and can
retrieve the value of the variables at any time without performance
overhead. The framework makes sure that the variables do not change
during the SIP message processing, the child processes see a snapshot
of the variables with constant values. The variable, that is changed by
a cfg driver module, will be automatically replaced by the framework
the next time a SIP message is started to be processed.
  
The drivers can change the values of all the variables by names with or
without the need of commit. That means a kind of transaction support,
the framework can keep track of the changes (per driver) until they
are committed or rolled-back.

2. Using the framework in a module
===============================================================================

Make sure that the run-time change of the variable cannot cause troubles.
You can expect the variable change before a SIP message is processed,
or before a timer fires, but it will never change during the function
calls.

1. Include the header file:

#include "../../cfg/cfg.h"

-------------------------------------------------------------------------------

2. Define a structure that contains the variables, the structure name
must begin with "cfg_group_" followed by the group name: (The group name
is typically the module name, but a single module can register more than
one groups as well.)

struct cfg_group_foo {
	int	i;
	char	*ch;
	str	s;
	void	*p;
};

-------------------------------------------------------------------------------

3. Set the default values:

static struct cfg_group_foo default_cfg = {
	-1,
	"mystring",
	{"interoperability", 16},
	NULL,
};

-------------------------------------------------------------------------------

4. Export the variables over the module interface if you wish:

static param_export_t params[] = {
	{"i",	PARAM_INT,	&default_cfg.i},
	{"ch",	PARAM_STRING,	&default_cfg.ch},
	{"s",	PARAM_STR,	&default_cfg.s},
	{0, 0, 0}
};

-------------------------------------------------------------------------------

5. Declare a void* handle that will be used to access the config group:

static void	*cfg_handle = &default_cfg;

-------------------------------------------------------------------------------

6. Describe the structure you defined at step 2 for the framework:

static cfg_def_t cfg_def[] = {
	{"int", CFG_VAR_INT, -10, 10, 0, 0, "integer for testing"},
	{"ch", CFG_VAR_STRING, 0, 0, 0, 0, "string for testing"},
	{"s", CFG_VAR_STR, 0, 0, 0, 0, "str for testing"},
	{"p", CFG_VAR_POINTER | CFG_INPUT_STRING, 0, 0, fixup_p, fixup_child_p, "pointer for testing"},
	{0, 0, 0, 0, 0, 0, 0},
};

Each row consists of the following items:

- name that will be used by the drivers to refer to the variable
- flag indicating the variable and the input type, that is accepted
  by the fixup function

  Valid variable types are:
	- CFG_VAR_INT		= int
	- CFG_VAR_STRING	= char*
	- CFG_VAR_STR		= str
	- CFG_VAR_POINTER	= void*

  Valid input types are:
	- CFG_INPUT_INT		= int
	- CFG_INPUT_STRING	= char*
	- CFG_INPUT_STR		= str*

- minimum value for integers (optional)
- maximum value for integers (optional)
- fixup function (optional) that is called when the variable is going to be
  changed by a driver. The module still uses the old value until the change
  is committed, and the next SIP message is started to be processed, however
  the new, not-yet-committed values can be accessed by using the temporary
  handle within the fixup function. String and str values are cloned to
  shm memory by the framework. The callback type is:

  typedef int (*cfg_on_change)(void *temp_handle, str *var_name, void **value);

- per-child process callback function (optional) that is called by each child
  process separately, after the new values have been committed, and the
  child process is updating its local configuration. The old value will no
  longer be used by the process. (Useful for fix-ups that cannot be done
  in shm memory, for example regexp compilation.)

  typedef void (*cfg_on_set_child)(str *var_name);

- description of the variable

-------------------------------------------------------------------------------

7. Declare the configuration group in mod_init:

static int mod_init(void)
{
	if (cfg_declare("foo", cfg_def, &default_cfg, cfg_size(foo),
			 &cfg_handle)
	) {
		/* error */
		return -1;
	}
	...
}

-------------------------------------------------------------------------------

8. The variables can be accessed any time by the group name and handle:

cfg_get(foo, cfg_handle, i)
cfg_get(foo, cfg_handle, ch)
cfg_get(foo, cfg_handle, s)
cfg_get(foo, cfg_handle, p)


3. Using the framework in the core
===============================================================================

There is basically no difference between the modules and the core, the core can
register any number of groups just like a module. A group called "core" has
been already registered, have a look at the cfg_core.* files.


4. Drivers
===============================================================================

Drivers can change the values of the configuration variables run-time, they can
implement RPC calls or database backend for example.
The framework is multi-process safe, more drivers (or a single driver with
multiple processes) can modify the configuration at the same time.

1. Create a context for the driver

#include "../../cfg/cfg_ctx.h"

static cfg_ctx_t	*ctx = NULL;

static void on_declare(str *group_name, cfg_def_t *definition)
{
	...
}

static int mod_init(void)
{
	ctx = cfg_register_ctx(on_declare);
	if (!ctx) {
		/* error */
		return -1;
	}
	...
}

The callback function, on_declare(), is called every time a new configuration
group is registered, so the driver has a chance to know which groups and
variables are present, and can immediately modify them.

-------------------------------------------------------------------------------

2. Get the value of a variable by name:

cfg_get_by_name()

-------------------------------------------------------------------------------

3. Set the value of a variable without the need of explicit commit:

cfg_set_now()

wrapper functions:

cfg_set_now_int()
cfg_set_now_string()

-------------------------------------------------------------------------------

4. Set the value of a variable, but does not commit the change:

cfg_set_delayed()

wrapper functions:

cfg_set_delayed_int()
cfg_set_delayed_string()

More changes can be done, and committed at once.

-------------------------------------------------------------------------------

5. Commit or roll back the previously prepared changes:

cfg_commit()
cfg_rollback()

-------------------------------------------------------------------------------

6. Get the description of a variable:

cfg_help()

-------------------------------------------------------------------------------

7. Get the list of group definitions:

void	*h;
str	gname;
cfg_def_t	*def;

cfg_get_group_init(&h);
while(cfg_get_group_next(&h, &gname, &def)) {
	...
}

-------------------------------------------------------------------------------

8. Get the list of pending changes that have not been committed yet:

void		*h;
str		gname, vname;
void		*old_val, *new_val;
unsigned int	val_type;

if (cfg_diff_init(ctx, &h)) return -1;
while(cfg_diff_next(&h,
		&gname, &vname,
		&old_val, &new_val,
		&val_type)
) {
	...
}
cfg_diff_release(ctx);


5. Refreshing the configuration
===============================================================================

There is no need to refresh the configuration in the modules, the core takes
care of it, unless the module forks a new process that runs in an endless
loop. In this case, it is the task of the forked child process to periodically
update its own local configuration the following way:


#include "../../cfg/cfg_struct.h"

void loop_forever(void)
{
	while(1) {
		/* update the local config */
		cfg_update();
		...
	}
}

int child_init(int rank)
{
	int	pid;

	pid = fork_process(PROC_NOCHLDINIT, "foo", 1);
	if (pid == 0) {
		/* This is the child process */

		/* initialize the config framework */
		if (cfg_child_init()) return -1;

		loop_forever(); /* never returns */
	}
	...
}

The local configuration must be destroyed only when the child process exits,
but SER continues running, so the module keeps forking and destroying child
processes runtime. Calling the configuration destroy function must be the
very last action of the child process before it exists:

int new_process(void)
{
	int	pid;

	pid = fork();

	if (pid == 0) {
		/* This is the child process */

		/* initialize the config framework */
		if (cfg_child_init()) return -1;

		loop_forever(); /* the function may return */

		/* destroy the local config */
		cfg_child_destroy();
		exit(0);
	}
}

Note, that the configuration should be refreshed even if the module does not
declare any config variable, because other modules and the core may need the
up-to-date config.
