Implementing a configuration framework, that can be used by SER core,
authorMiklos Tirpak <miklos@iptel.org>
Wed, 5 Dec 2007 15:22:01 +0000 (15:22 +0000)
committerMiklos Tirpak <miklos@iptel.org>
Wed, 5 Dec 2007 15:22:01 +0000 (15:22 +0000)
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 value 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.

cfg/cfg.c [new file with mode: 0644]
cfg/cfg.h [new file with mode: 0644]
cfg/cfg_ctx.c [new file with mode: 0644]
cfg/cfg_ctx.h [new file with mode: 0644]
cfg/cfg_struct.c [new file with mode: 0644]
cfg/cfg_struct.h [new file with mode: 0644]

diff --git a/cfg/cfg.c b/cfg/cfg.c
new file mode 100644 (file)
index 0000000..ffb7b82
--- /dev/null
+++ b/cfg/cfg.c
@@ -0,0 +1,152 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#include <string.h>
+
+#include "../ut.h"
+#include "../mem/mem.h"
+#include "cfg_struct.h"
+#include "cfg_ctx.h"
+#include "cfg.h"
+
+/* declares a new cfg group
+ * handler is set to the memory area where the variables are stored
+ * return value is -1 on error
+ */
+int cfg_declare(char *group_name, cfg_def_t *def, void *values, int def_size,
+                       void **handle)
+{
+       int     i, num, size;
+       cfg_mapping_t   *mapping = NULL;
+
+       /* check the number of the variables */
+       for (num=0; def[num].name; num++);
+
+       mapping = (cfg_mapping_t *)pkg_malloc(sizeof(cfg_mapping_t)*num);
+       if (!mapping) {
+               LOG(L_ERR, "ERROR: register_cfg_def(): not enough memory\n");
+               goto error;
+       }
+       memset(mapping, 0, sizeof(cfg_mapping_t)*num);
+
+       /* calculate the size of the memory block that has to
+       be allocated for the cfg variables, and set the content of the 
+       cfg_mapping array the same time */
+       for (i=0, size=0; i<num; i++) {
+               mapping[i].def = &(def[i]);
+               mapping[i].name_len = strlen(def[i].name);
+
+               /* padding depends on the type of the next variable */
+               switch (CFG_VAR_MASK(def[i].type)) {
+
+               case CFG_VAR_INT:
+                       size = ROUND_INT(size);
+                       mapping[i].offset = size;
+                       size += sizeof(int);
+                       break;
+
+               case CFG_VAR_STRING:
+               case CFG_VAR_POINTER:
+                       size = ROUND_POINTER(size);
+                       mapping[i].offset = size;
+                       size += sizeof(char *);
+                       break;
+
+               case CFG_VAR_STR:
+                       size = ROUND_POINTER(size);
+                       mapping[i].offset = size;
+                       size += sizeof(str);
+                       break;
+
+               default:
+                       LOG(L_ERR, "ERROR: register_cfg_def(): %s.%s: unsupported variable type\n",
+                                       group_name, def[i].name);
+                       goto error;
+               }
+
+               /* verify the type of the input */
+               if (CFG_INPUT_MASK(def[i].type)==0) {
+                       def[i].type |= def[i].type << 3;
+               } else {
+                       if ((CFG_INPUT_MASK(def[i].type) != CFG_VAR_MASK(def[i].type) << 3)
+                       && (def[i].on_change_cb == 0)) {
+                               LOG(L_ERR, "ERROR: register_cfg_def(): %s.%s: variable and input types are "
+                                       "different, but no callback is defined for conversion\n",
+                                       group_name, def[i].name);
+                               goto error;
+                       }
+               }
+
+               if (CFG_INPUT_MASK(def[i].type) > CFG_INPUT_STR) {
+                       LOG(L_ERR, "ERROR: register_cfg_def(): %s.%s: unsupported input type\n",
+                                       group_name, def[i].name);
+                       goto error;
+               }
+       }
+
+       /* minor validation */
+       if (size != def_size) {
+               LOG(L_ERR, "ERROR: register_cfg_def(): the specified size of the config "
+                       "structure does not equal with the calculated size, check whether "
+                       "the variable types are correctly defined!\n");
+               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, num, mapping, values, size, handle))
+               goto error;
+
+       /* The cfg variables are ready to use, let us set the handle
+       before passing the new definitions to the drivers.
+       We make the interface usable for the fixup functions
+       at this step */
+       *handle = values;
+
+       /* notify the drivers about the new config definition */
+       cfg_notify_drivers(group_name, def);
+
+       LOG(L_DBG, "DEBUG: register_cfg_def(): "
+               "new config group has been registered: '%s' (num=%d, size=%d)\n",
+               group_name, num, size);
+
+       /* TODO: inform the drivers about the new definition */
+
+       return 0;
+
+error:
+       if (mapping) pkg_free(mapping);
+       LOG(L_ERR, "ERROR: register_cfg_def(): Failed to register the config group: %s\n",
+                       group_name);
+
+       return -1;
+}
diff --git a/cfg/cfg.h b/cfg/cfg.h
new file mode 100644 (file)
index 0000000..a828bb9
--- /dev/null
+++ b/cfg/cfg.h
@@ -0,0 +1,76 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#ifndef _CFG_H
+#define _CFG_H
+
+#include "../str.h"
+
+#define CFG_VAR_INT            1U
+#define CFG_VAR_STRING         2U
+#define CFG_VAR_STR            3U
+#define CFG_VAR_POINTER                4U
+
+#define CFG_INPUT_INT          (CFG_VAR_INT << 3)
+#define CFG_INPUT_STRING       (CFG_VAR_STRING << 3)
+#define CFG_INPUT_STR          (CFG_VAR_STR << 3)
+
+#define CFG_VAR_MASK(x)                ((x)&(CFG_INPUT_INT-1))
+#define CFG_INPUT_MASK(x)      ((x)&(~(CFG_INPUT_INT-1)))
+
+typedef int (*cfg_on_change)(void *, str *, void **);
+typedef void (*cfg_on_set_child)(str *);
+
+/* strutrure to be used buy the module interface */
+typedef struct _cfg_def {
+       char    *name;
+       unsigned int    type;
+       int     min;
+       int     max;
+       cfg_on_change   on_change_cb;
+       cfg_on_set_child        on_set_child_cb;
+       char    *descr;
+} cfg_def_t;
+
+/* declares a new cfg group
+ * handler is set to the memory area where the variables are stored
+ * return value is -1 on error
+ */
+int cfg_declare(char *group_name, cfg_def_t *def, void *values, int def_size,
+                       void **handler);
+
+#define cfg_size(gname) \
+       sizeof(struct cfg_group_##gname)
+
+#define cfg_get(gname, handle, var) \
+       ((struct cfg_group_##gname *)handle)->var
+
+#endif /* _CFG_H */
diff --git a/cfg/cfg_ctx.c b/cfg/cfg_ctx.c
new file mode 100644 (file)
index 0000000..8403396
--- /dev/null
@@ -0,0 +1,773 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#include <string.h>
+
+#include "cfg_struct.h"
+#include "cfg_ctx.h"
+
+/* linked list of all the registered cfg contexts */
+static cfg_ctx_t       *cfg_ctx_list = NULL;
+
+/* creates a new config context that is an interface to the
+ * cfg variables with write permission
+ */
+cfg_ctx_t *cfg_register_ctx(cfg_on_declare on_declare_cb)
+{
+       cfg_ctx_t       *ctx;
+       cfg_group_t     *group;
+       str             gname;
+
+       /* allocate memory for the new context
+       Better to use shm mem, because 'changed' and 'lock'
+       must be in shm mem anyway */
+       ctx = (cfg_ctx_t *)shm_malloc(sizeof(cfg_ctx_t));
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_register_ctx(): not enough shm memory\n");
+               return NULL;
+       }
+       memset(ctx, 0, sizeof(cfg_ctx_t));
+       if (lock_init(&ctx->lock) == 0) {
+               LOG(L_ERR, "ERROR: cfg_register_ctx(): failed to init lock\n");
+               shm_free(ctx);
+               return NULL;
+       }
+
+       /* add the new ctx to the beginning of the list */
+       ctx->next = cfg_ctx_list;
+       cfg_ctx_list = ctx;
+
+       /* let the driver know about the already registered groups */
+       if (on_declare_cb) {
+               ctx->on_declare_cb = on_declare_cb;
+
+               for (   group = cfg_group;
+                       group;
+                       group = group->next
+               ) {
+                       gname.s = group->name;
+                       gname.len = group->name_len;
+                       on_declare_cb(&gname, group->mapping->def);
+               }
+       }
+
+       return ctx;
+}
+
+/* free the memory allocated for the contexts */
+void cfg_ctx_destroy(void)
+{
+       cfg_ctx_t       *ctx, *ctx2;
+
+       for (   ctx = cfg_ctx_list;
+               ctx;
+               ctx = ctx2
+       ) {
+               ctx2 = ctx->next;
+               shm_free(ctx);
+       }
+       cfg_ctx_list = NULL;
+}
+
+/* notify the drivers about the new config definition */
+void cfg_notify_drivers(char *group_name, cfg_def_t *def)
+{
+       cfg_ctx_t       *ctx;
+       str             gname;
+
+       gname.s = group_name;
+       gname.len = strlen(group_name);
+
+       for (   ctx = cfg_ctx_list;
+               ctx;
+               ctx = ctx->next
+       )
+               if (ctx->on_declare_cb)
+                       ctx->on_declare_cb(&gname, def);
+}
+
+/* convert the value to the requested type
+ * (only string->str is implemented currently) */
+static int convert_val(unsigned int val_type, void *val,
+                       unsigned int var_type, void **new_val)
+{
+       static str      s;
+
+       switch (val_type) {
+               case CFG_VAR_INT:
+                       if (CFG_INPUT_MASK(var_type) != CFG_INPUT_INT)
+                               goto error;
+                       *new_val = val;
+                       break;
+
+               case CFG_VAR_STRING:
+                       if (CFG_INPUT_MASK(var_type) == CFG_INPUT_STR) {
+                               s.s = val;
+                               s.len = strlen(s.s);
+                               *new_val = (void *)&s;
+                               break;
+                       }
+                       if (CFG_INPUT_MASK(var_type) != CFG_INPUT_STRING)
+                               goto error;
+                       *new_val = val;
+                       break;
+               default:
+                       goto error;
+       }
+
+       return 0;
+
+error:
+       LOG(L_ERR, "ERROR: convert_val(): got a value with type %u, but expected %u\n",
+                       val_type, CFG_INPUT_MASK(var_type));
+       return -1;
+}
+
+/* sets the value of a variable without the need of commit */
+int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
+                       void *val, unsigned int val_type)
+{
+       cfg_group_t     *group;
+       cfg_mapping_t   *var;
+       void            *p, *v;
+       cfg_block_t     *block = NULL;
+       str             s;
+       char            *old_string = NULL;
+       char            **replaced = NULL;
+       cfg_child_cb_t  *child_cb = NULL;
+       int             i;
+
+       /* verify the context even if we do not need it now
+       to make sure that a cfg driver has called the function
+       (very very weak security) */
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_set_now(): context is undefined\n");
+               return -1;
+       }
+
+       /* look-up the group and the variable */
+       if (cfg_lookup_var(group_name, var_name, &group, &var))
+               return -1;
+
+       /* check whether we have to convert the type */
+       if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
+               goto error0;
+       
+       if (var->def->on_change_cb) {
+               /* Call the fixup function.
+               There is no need to set a temporary cfg handle,
+               becaue a single variable is changed */
+               if (var->def->on_change_cb(*(group->handle),
+                                               var_name,
+                                               &v) < 0) {
+                       LOG(L_ERR, "ERROR: cfg_set_now(): fixup failed\n");
+                       goto error0;
+               }
+
+       } else if ((CFG_VAR_TYPE(var) == CFG_VAR_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_set_now(): integer value is out of range\n");
+                       goto error0;
+               }
+       }
+
+       if (cfg_shmized) {
+               if (var->def->on_set_child_cb) {
+                       child_cb = cfg_child_cb_new(var_name,
+                                               var->def->on_set_child_cb);
+                       if (!child_cb) {
+                               LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
+                               goto error0;
+                       }
+               }
+
+               /* make sure that nobody else replaces the global config
+               while the new one is prepared */
+               CFG_WRITER_LOCK();
+
+               /* clone the memory block, and prepare the modification */
+               if (!(block = cfg_clone_global())) goto error;
+
+               p = block->vars+group->offset+var->offset;
+       } else {
+               /* we are allowed to rewrite the value on-the-fly */
+               p = group->vars + var->offset;
+       }
+
+       /* set the new value */
+       switch (CFG_VAR_TYPE(var)) {
+       case CFG_VAR_INT:
+               i = (int)(long)v;
+               memcpy(p, &i, sizeof(int));
+               break;
+
+       case CFG_VAR_STRING:
+               /* clone the string to shm mem */
+               s.s = v;
+               s.len = strlen(v);
+               if (!(s.s = cfg_clone_str(s))) goto error;
+               memcpy(&old_string, p, sizeof(char *));
+               memcpy(p, &s.s, sizeof(char *));
+               break;
+
+       case CFG_VAR_STR:
+               /* clone the string to shm mem */
+               s = *(str *)v;
+               if (!(s.s = cfg_clone_str(s))) goto error;
+               memcpy(&old_string, p, sizeof(char *));
+               memcpy(p, &s, sizeof(str));
+               break;
+
+       case CFG_VAR_POINTER:
+               memcpy(p, &v, sizeof(void *));
+               break;
+
+       }
+
+       if (cfg_shmized) {
+               if (old_string) {
+                       /* prepare the array of the replaced strings,
+                       they will be freed when the old block is freed */
+                       replaced = (char **)shm_malloc(sizeof(char *)*2);
+                       if (!replaced) {
+                               LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
+                               goto error;
+                       }
+                       replaced[0] = old_string;
+                       replaced[1] = NULL;
+               }
+               /* replace the global config with the new one */
+               cfg_install_global(block, replaced, child_cb, child_cb);
+               CFG_WRITER_UNLOCK();
+       } else {
+               /* flag the variable because there is no need
+               to shmize it again */
+               var->flag |= cfg_var_shmized;
+       }
+
+       if (val_type == CFG_VAR_INT)
+               LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
+                       "has been changed to %d\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s,
+                       (int)(long)val);
+       else
+               LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
+                       "has been changed to \"%s\"\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s,
+                       (char *)val);
+
+       return 0;
+
+error:
+       if (cfg_shmized) CFG_WRITER_UNLOCK();
+       if (block) cfg_block_free(block);
+       if (child_cb) cfg_child_cb_free(child_cb);
+
+error0:
+       LOG(L_ERR, "ERROR: cfg_set_now(): failed to set the variable: %.*s.%.*s\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s);
+
+
+       return -1;
+}
+
+/* wrapper function for cfg_set_now */
+int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
+{
+       return cfg_set_now(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
+}
+
+/* wrapper function for cfg_set_now */
+int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
+{
+       return cfg_set_now(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
+}
+
+/* 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_sizeK(): unknown type: %u\n",
+                       CFG_VAR_TYPE(var));
+               return 0;
+       }
+}
+
+/* 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,
+                       void *val, unsigned int val_type)
+{
+       cfg_group_t     *group;
+       cfg_mapping_t   *var;
+       void            *v;
+       char            *temp_handle;
+       int             temp_handle_created;
+       cfg_changed_var_t       *changed = NULL;
+       int             i, size;
+       str             s;
+
+       if (!cfg_shmized)
+               /* the cfg has not been shmized yet, there is no
+               point in registering the change and committing it later */
+               return cfg_set_now(ctx, group_name, var_name,
+                                       val, val_type);
+
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_set_delayed(): context is undefined\n");
+               return -1;
+       }
+
+       /* look-up the group and the variable */
+       if (cfg_lookup_var(group_name, var_name, &group, &var))
+               return -1;
+
+       /* check whether we have to convert the type */
+       if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
+               goto error0;
+
+       /* the ctx must be locked while reading and writing
+       the list of changed variables */
+       CFG_CTX_LOCK(ctx);
+
+       if (var->def->on_change_cb) {
+               /* The fixup function must see also the
+               not yet committed values, so a temporary handle
+               must be prepared that points to the new config.
+               Only the values within the group are applied,
+               other modifications are not visible to the callback.
+               The local config is the base. */
+
+               if (ctx->changed_first) {
+                       temp_handle = (char *)pkg_malloc(group->size);
+                       if (!temp_handle) {
+                               LOG(L_ERR, "ERROR: cfg_set_delayed(): "
+                                       "not enough memory\n");
+                               goto error;
+                       }
+                       temp_handle_created = 1;
+                       memcpy(temp_handle, *(group->handle), group->size);
+
+                       /* apply the changes */
+                       for (   changed = ctx->changed_first;
+                               changed;
+                               changed = changed->next
+                       ) {
+                               if (changed->group != group) continue;
+
+                               memcpy( temp_handle + changed->var->offset,
+                                       changed->new_val,
+                                       cfg_var_size(changed->var));
+                       }
+               } else {
+                       /* there is not any change */
+                       temp_handle = *(group->handle);
+                       temp_handle_created = 0;
+               }
+                       
+               if (var->def->on_change_cb(temp_handle,
+                                               var_name,
+                                               &v) < 0) {
+                       LOG(L_ERR, "ERROR: cfg_set_delayed(): fixup failed\n");
+                       if (temp_handle_created) pkg_free(temp_handle);
+                       goto error;
+               }
+               if (temp_handle_created) pkg_free(temp_handle);
+
+       } else if ((CFG_VAR_TYPE(var) == CFG_VAR_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_set_delayed(): integer value is out of range\n");
+                       goto error;
+               }
+       }
+
+       /* everything went ok, we can add the new value to the list */
+       size = sizeof(cfg_changed_var_t) + cfg_var_size(var) - 1;
+       changed = (cfg_changed_var_t *)shm_malloc(size);
+       if (!changed) {
+               LOG(L_ERR, "ERROR: cfg_set_delayed(): not enough shm memory\n");
+               goto error;
+       }
+       memset(changed, 0, size);
+       changed->group = group;
+       changed->var = var;
+
+       switch (CFG_VAR_TYPE(var)) {
+
+       case CFG_VAR_INT:
+               i = (int)(long)v;
+               memcpy(changed->new_val, &i, sizeof(int));
+               break;
+
+       case CFG_VAR_STRING:
+               /* clone the string to shm mem */
+               s.s = v;
+               s.len = strlen(v);
+               if (!(s.s = cfg_clone_str(s))) goto error;
+               memcpy(changed->new_val, &s.s, sizeof(char *));
+               break;
+
+       case CFG_VAR_STR:
+               /* clone the string to shm mem */
+               s = *(str *)v;
+               if (!(s.s = cfg_clone_str(s))) goto error;
+               memcpy(changed->new_val, &s, sizeof(str));
+               break;
+
+       case CFG_VAR_POINTER:
+               memcpy(changed->new_val, &v, sizeof(void *));
+               break;
+
+       }
+
+       /* 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;
+
+       CFG_CTX_UNLOCK(ctx);
+
+       if (val_type == CFG_VAR_INT)
+               LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
+                       "is going to be changed to %d "
+                       "[context=%p]\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s,
+                       (int)(long)val,
+                       ctx);
+       else
+               LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
+                       "is going to be changed to \"%s\" "
+                       "[context=%p]\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s,
+                       (char *)val,
+                       ctx);
+
+       return 0;
+
+error:
+       CFG_CTX_UNLOCK(ctx);
+       if (changed) shm_free(changed);
+error0:
+       LOG(L_ERR, "ERROR: cfg_set_delayed(): failed to set the variable: %.*s.%.*s\n",
+                       group_name->len, group_name->s,
+                       var_name->len, var_name->s);
+
+       return -1;
+}
+
+/* wrapper function for cfg_set_delayed */
+int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
+{
+       return cfg_set_delayed(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
+}
+
+/* wrapper function for cfg_set_delayed */
+int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
+{
+       return cfg_set_delayed(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
+}
+
+/* commits the previously prepared changes within the context */
+int cfg_commit(cfg_ctx_t *ctx)
+{
+       int     replaced_num = 0;
+       cfg_changed_var_t       *changed, *changed2;
+       cfg_block_t     *block;
+       char    **replaced = NULL;
+       cfg_child_cb_t  *child_cb;
+       cfg_child_cb_t  *child_cb_first = NULL;
+       cfg_child_cb_t  *child_cb_last = NULL;
+       int     size;
+       void    *p;
+       str     s;
+
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_commit(): context is undefined\n");
+               return -1;
+       }
+
+       if (!cfg_shmized) return 0; /* nothing to do */
+
+       /* the ctx must be locked while reading and writing
+       the list of changed variables */
+       CFG_CTX_LOCK(ctx);
+
+       /* is there any change? */
+       if (!ctx->changed_first) goto done;
+
+       /* 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;
+               changed;
+               changed = changed->next
+       ) {
+               if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
+               || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
+                       replaced_num++;
+
+
+               if (changed->var->def->on_set_child_cb) {
+                       s.s = changed->var->def->name;
+                       s.len = changed->var->name_len;
+                       child_cb = cfg_child_cb_new(&s,
+                                       changed->var->def->on_set_child_cb);
+                       if (!child_cb) goto error0;
+
+                       if (child_cb_last)
+                               child_cb_last->next = child_cb;
+                       else
+                               child_cb_first = child_cb;
+                       child_cb_last = child_cb;
+               }
+       }
+
+       /* allocate memory for the replaced string array */
+       size = sizeof(char *)*(replaced_num + 1);
+       replaced = (char **)shm_malloc(size);
+       if (!replaced) {
+               LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
+               goto error;
+       }
+       memset(replaced, 0 , size);
+
+       /* make sure that nobody else replaces the global config
+       while the new one is prepared */
+       CFG_WRITER_LOCK();
+
+       /* clone the memory block, and prepare the modification */
+       if (!(block = cfg_clone_global())) {
+               CFG_WRITER_UNLOCK();
+               goto error;
+       }
+
+       /* apply the modifications to the buffer */
+       replaced_num = 0;
+       for (   changed = ctx->changed_first;
+               changed;
+               changed = changed->next
+       ) {
+               p = block->vars
+                       + changed->group->offset
+                       + changed->var->offset;
+
+               if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
+               || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) {
+                       memcpy(&(replaced[replaced_num]), p, sizeof(char *));
+                       replaced_num++;
+               }
+
+               memcpy( p,
+                       changed->new_val,
+                       cfg_var_size(changed->var));
+       }
+
+       /* replace the global config with the new one */
+       cfg_install_global(block, replaced, child_cb_first, child_cb_last);
+       CFG_WRITER_UNLOCK();
+
+       /* free the changed list */     
+       for (   changed = ctx->changed_first;
+               changed;
+               changed = changed2
+       ) {
+               changed2 = changed->next;
+               shm_free(changed);
+       }
+       ctx->changed_first = NULL;
+       ctx->changed_last = NULL;
+
+done:
+       LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
+                       "[context=%p]\n",
+                       ctx);
+
+       CFG_CTX_UNLOCK(ctx);
+       return 0;
+
+error:
+       CFG_CTX_UNLOCK(ctx);
+
+error0:
+
+       if (child_cb_first) cfg_child_cb_free(child_cb_first);
+       if (replaced) shm_free(replaced);
+
+       return -1;
+}
+
+/* drops the not yet committed changes within the context */
+int cfg_rollback(cfg_ctx_t *ctx)
+{
+       cfg_changed_var_t       *changed, *changed2;
+       char    *new_string;
+
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_rollback(): context is undefined\n");
+               return -1;
+       }
+
+       if (!cfg_shmized) return 0; /* nothing to do */
+
+       LOG(L_INFO, "INFO: cfg_rollback(): deleting the config changes "
+                       "[context=%p]\n",
+                       ctx);
+
+       /* the ctx must be locked while reading and writing
+       the list of changed variables */
+       CFG_CTX_LOCK(ctx);
+
+       for (   changed = ctx->changed_first;
+               changed;
+               changed = changed2
+       ) {
+               changed2 = changed->next;
+
+               if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
+               || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) {
+                       memcpy(&new_string, changed->new_val, sizeof(char *));
+                       shm_free(new_string);
+               }
+               shm_free(changed);
+       }
+       ctx->changed_first = NULL;
+       ctx->changed_last = NULL;
+
+       CFG_CTX_UNLOCK(ctx);
+
+       return 0;
+}
+
+/* returns the value of a variable */
+int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
+                       void **val, unsigned int *val_type)
+{
+       cfg_group_t     *group;
+       cfg_mapping_t   *var;
+       void            *p;
+       static str      s;      /* we need the value even
+                               after the function returns */
+       int             i;
+       char            *ch;
+
+       /* verify the context even if we do not need it now
+       to make sure that a cfg driver has called the function
+       (very very weak security) */
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_get_by_name(): context is undefined\n");
+               return -1;
+       }
+
+       /* look-up the group and the variable */
+       if (cfg_lookup_var(group_name, var_name, &group, &var))
+               return -1;
+
+       /* use the module's handle to access the variable
+       It means that the variable is read from the local config
+       after forking */
+       p = *(group->handle) + var->offset;
+
+       switch (CFG_VAR_TYPE(var)) {
+       case CFG_VAR_INT:
+               memcpy(&i, p, sizeof(int));
+               *val = (void *)(long)i;
+               break;
+
+       case CFG_VAR_STRING:
+               memcpy(&ch, p, sizeof(char *));
+               *val = (void *)ch;
+               break;
+
+       case CFG_VAR_STR:
+               memcpy(&s, p, sizeof(str));
+               *val = (void *)&s;
+               break;
+
+       case CFG_VAR_POINTER:
+               memcpy(val, &p, sizeof(void *));
+               break;
+
+       }
+       *val_type = CFG_VAR_TYPE(var);
+
+       return 0;
+}
+
+/* returns the description of a variable */
+int cfg_help(cfg_ctx_t *ctx, str *group_name, str *var_name,
+                       char **ch)
+{
+       cfg_mapping_t   *var;
+
+       /* verify the context even if we do not need it now
+       to make sure that a cfg driver has called the function
+       (very very weak security) */
+       if (!ctx) {
+               LOG(L_ERR, "ERROR: cfg_help(): context is undefined\n");
+               return -1;
+       }
+
+       /* look-up the group and the variable */
+       if (cfg_lookup_var(group_name, var_name, NULL, &var))
+               return -1;
+
+       *ch = var->def->descr;
+       return 0;
+}
diff --git a/cfg/cfg_ctx.h b/cfg/cfg_ctx.h
new file mode 100644 (file)
index 0000000..9a5e566
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#ifndef _CFG_CTX_H
+#define _CFG_CTX_H
+
+#include "../str.h"
+#include "../locking.h"
+#include "cfg.h"
+#include "cfg_struct.h"
+
+/* linked list of variables with their new values */
+typedef struct _cfg_changed_var {
+       cfg_group_t     *group;
+       cfg_mapping_t   *var;
+       struct _cfg_changed_var *next;
+
+       /* blob that contains the new value */
+       unsigned char   new_val[1];
+} cfg_changed_var_t;
+
+/* callback that is called when a new group is declared */
+typedef void (*cfg_on_declare)(str *, cfg_def_t *);
+
+/* linked list of registered contexts */
+typedef struct _cfg_ctx {
+       /* variables that are already changed
+       but have not been committed yet */
+       cfg_changed_var_t       *changed_first;
+       cfg_changed_var_t       *changed_last;
+       /* lock potecting the the linked-list of
+       changed variables */
+       gen_lock_t              lock;
+
+       /* callback that is called when a new
+       group is registered */
+       cfg_on_declare          on_declare_cb;
+
+       struct _cfg_ctx *next;
+} cfg_ctx_t;
+
+#define CFG_CTX_LOCK(ctx)      lock_get(&(ctx)->lock)
+#define CFG_CTX_UNLOCK(ctx)    lock_release(&(ctx)->lock)
+
+/* creates a new config context that is an interface to the
+ * cfg variables with write permission */
+cfg_ctx_t *cfg_register_ctx(cfg_on_declare on_declare_cb);
+
+/* free the memory allocated for the contexts */
+void cfg_ctx_destroy(void);
+
+/* 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,
+                       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);
+
+/* 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,
+                       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);
+
+/* commits the previously prepared changes within the context */
+int cfg_commit(cfg_ctx_t *ctx);
+
+/* drops the not yet committed changes within the context */
+int cfg_rollback(cfg_ctx_t *ctx);
+
+/* returns the value of a variable */
+int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
+                       void **val, unsigned int *val_type);
+
+/* returns the description of a variable */
+int cfg_help(cfg_ctx_t *ctx, str *group_name, str *var_name,
+                       char **ch);
+
+/* notify the drivers about the new config definition */
+void cfg_notify_drivers(char *group_name, cfg_def_t *def);
+
+
+#endif /* _CFG_CTX_H */
diff --git a/cfg/cfg_struct.c b/cfg/cfg_struct.c
new file mode 100644 (file)
index 0000000..b3596fa
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#include <string.h>
+
+#include "../mem/mem.h"
+#include "../mem/shm_mem.h"
+#include "../ut.h"
+#include "../locking.h"
+#include "cfg_ctx.h"
+#include "cfg_struct.h"
+
+cfg_group_t    *cfg_group = NULL;      /* linked list of registered cfg groups */
+cfg_block_t    **cfg_global = NULL;    /* pointer to the active cfg block */
+cfg_block_t    *cfg_local = NULL;      /* per-process pointer to the active cfg block.
+                                       Updated only when the child process
+                                       finishes working on the SIP message */
+static int     cfg_block_size = 0;     /* size of the cfg block (constant) */
+gen_lock_t     *cfg_global_lock = 0;   /* protects *cfg_global */
+gen_lock_t     *cfg_writer_lock = 0;   /* This lock makes sure that two processes do not
+                                       try to clone *cfg_global at the same time.
+                                       Never try to get cfg_writer_lock when
+                                       cfg_global_lock is held */
+int            cfg_shmized = 0;        /* indicates whether the cfg block has been
+                                       already shmized */
+
+cfg_child_cb_t **cfg_child_cb_first = NULL;    /* first item of the per-child process
+                                               callback list */
+cfg_child_cb_t **cfg_child_cb_last = NULL;     /* last item of the above list */
+cfg_child_cb_t *cfg_child_cb = NULL;   /* pointer to the previously executed cb */     
+
+/* creates a new cfg group, and adds it to the linked list */
+int cfg_new_group(char *name, int num, cfg_mapping_t *mapping,
+               char *vars, int size, void **handle)
+{
+       cfg_group_t     *group;
+       int             len;
+
+       if (cfg_shmized) {
+               LOG(L_ERR, "ERROR: cfg_new_group(): too late config declaration\n");
+               return -1;
+       }
+
+       len = strlen(name);
+       group = (cfg_group_t *)pkg_malloc(sizeof(cfg_group_t)+len-1);
+       if (!group) {
+               LOG(L_ERR, "ERROR: cfg_new_group(): not enough memory\n");
+               return -1;
+       }
+       memset(group, 0, sizeof(cfg_group_t)+len-1);
+
+       group->num = num;
+       group->mapping = mapping;
+       group->vars = vars;
+       group->size = size;
+       group->handle = handle;
+       group->name_len = len;
+       memcpy(&group->name, name, len);
+
+       /* add the new group to the beginning of the list */
+       group->next = cfg_group;
+       cfg_group = group;
+
+       return 0;
+}
+
+/* clones a string to shared memory */
+char *cfg_clone_str(str s)
+{
+       char    *c;
+
+       c = (char *)shm_malloc(sizeof(char)*(s.len+1));
+       if (!c) {
+               LOG(L_ERR, "ERROR: cfg_clone_str(): not enough shm memory\n");
+               return NULL;
+       }
+       memcpy(c, s.s, s.len);
+       c[s.len] = '\0';
+
+       return c;
+}
+
+/* copies the strings to shared memory */
+static int cfg_shmize_strings(cfg_group_t *group)
+{
+       cfg_mapping_t   *mapping;
+       int     i;
+       str     s;
+
+       /* We do not know in advance whether the variable will be changed or not,
+       and it can happen that we try to free the shm memory area when the variable
+       is changed, hence, it must be already in shm mem */
+       mapping = group->mapping;
+       for (i=0; i<group->num; i++) {
+               /* the cfg driver module may have already shmized the variable */
+               if (mapping[i].flag & cfg_var_shmized) continue;
+
+               if (CFG_VAR_TYPE(&mapping[i]) == CFG_VAR_STRING) {
+                       memcpy(&s.s, group->vars + mapping[i].offset, sizeof(char *));
+                       s.len = strlen(s.s);
+
+               } else if (CFG_VAR_TYPE(&mapping[i]) == CFG_VAR_STR) {
+                       memcpy(&s, group->vars + mapping[i].offset, sizeof(str));
+
+               } else {
+                       continue;
+               }
+               if (!(s.s = cfg_clone_str(s))) return -1;
+               memcpy(group->vars + mapping[i].offset, &s.s, sizeof(char *));
+               mapping[i].flag |= cfg_var_shmized;
+       }
+
+       return 0;
+}
+
+/* copy the variables to shm mem */
+int cfg_shmize(void)
+{
+       cfg_group_t     *group;
+       cfg_block_t     *block = NULL;
+       int     size;
+
+       if (!cfg_group) return 0;
+
+       /* Let us allocate one memory block that
+       will contain all the variables */
+       for (   size=0, group = cfg_group;
+               group;
+               group=group->next
+       ) {
+               size = ROUND_POINTER(size);
+               group->offset = size;
+               size += group->size;
+       }
+
+       block = (cfg_block_t*)shm_malloc(sizeof(cfg_block_t)+size-1);
+       if (!block) {
+               LOG(L_ERR, "ERROR: cfg_clone_str(): not enough shm memory\n");
+               goto error;
+       }
+       memset(block, 0, sizeof(cfg_block_t)+size-1);
+       cfg_block_size = size;
+
+       /* copy the memory fragments to the single block */
+       for (   group = cfg_group;
+               group;
+               group=group->next
+       ) {
+               /* clone the strings to shm mem */
+               if (cfg_shmize_strings(group)) goto error;
+
+               /* copy the values to the new block,
+               and update the module's handle */
+               memcpy(block->vars+group->offset, group->vars, group->size);
+               *(group->handle) = block->vars+group->offset;
+       }
+
+       /* install the new config */
+       cfg_install_global(block, NULL, NULL, NULL);
+       cfg_shmized = 1;
+
+       return 0;
+
+error:
+       if (block) shm_free(block);
+       return -1;
+}
+
+/* initiate the cfg framework */
+int cfg_init(void)
+{
+       cfg_global_lock = lock_alloc();
+       if (!cfg_global_lock) {
+               LOG(L_ERR, "ERROR: cfg_init(): not enough shm memory\n");
+               goto error;
+       }
+       if (lock_init(cfg_global_lock) == 0) {
+               LOG(L_ERR, "ERROR: cfg_init(): failed to init lock\n");
+               lock_dealloc(cfg_global_lock);
+               cfg_global_lock = 0;
+               goto error;
+       }
+
+       cfg_writer_lock = lock_alloc();
+       if (!cfg_writer_lock) {
+               LOG(L_ERR, "ERROR: cfg_init(): not enough shm memory\n");
+               goto error;
+       }
+       if (lock_init(cfg_writer_lock) == 0) {
+               LOG(L_ERR, "ERROR: cfg_init(): failed to init lock\n");
+               lock_dealloc(cfg_writer_lock);
+               cfg_writer_lock = 0;
+               goto error;
+       }
+
+       cfg_global = (cfg_block_t **)shm_malloc(sizeof(cfg_block_t *));
+       if (!cfg_global) {
+               LOG(L_ERR, "ERROR: cfg_init(): not enough shm memory\n");
+               goto error;
+       }
+       *cfg_global = NULL;
+
+       cfg_child_cb_first = (cfg_child_cb_t **)shm_malloc(sizeof(cfg_child_cb_t *));
+       if (!cfg_child_cb_first) {
+               LOG(L_ERR, "ERROR: cfg_init(): not enough shm memory\n");
+               goto error;
+       }
+       *cfg_child_cb_first = NULL;
+
+       cfg_child_cb_last = (cfg_child_cb_t **)shm_malloc(sizeof(cfg_child_cb_t *));
+       if (!cfg_child_cb_last) {
+               LOG(L_ERR, "ERROR: cfg_init(): not enough shm memory\n");
+               goto error;
+       }
+       *cfg_child_cb_last = NULL;
+
+       /* A new cfg_child_cb struct must be created with a NULL callback function.
+       This stucture will be the entry point for the child processes, and
+       will be freed later, when none of the processes refers to it */
+       *cfg_child_cb_first = *cfg_child_cb_last =
+               cfg_child_cb_new(NULL, NULL);
+
+       if (!*cfg_child_cb_first) goto error;
+
+       return 0;
+
+error:
+       cfg_destroy();
+
+       return -1;
+}
+
+/* destroy the memory allocated for the cfg framework */
+void cfg_destroy(void)
+{
+       /* free the contexts */
+       cfg_ctx_destroy();
+
+       if (cfg_child_cb_first) {
+               if (*cfg_child_cb_first) cfg_child_cb_free(*cfg_child_cb_first);
+               shm_free(cfg_child_cb_first);
+               cfg_child_cb_first = NULL;
+       }
+
+       if (cfg_child_cb_last) {
+               shm_free(cfg_child_cb_last);
+               cfg_child_cb_last = NULL;
+       }
+
+       if (cfg_global) {
+               if (*cfg_global) cfg_block_free(*cfg_global);
+               shm_free(cfg_global);
+               cfg_global = NULL;
+       }
+       if (cfg_global_lock) {
+               lock_destroy(cfg_global_lock);
+               lock_dealloc(cfg_global_lock);
+               cfg_global_lock = 0;
+       }
+       if (cfg_writer_lock) {
+               lock_destroy(cfg_writer_lock);
+               lock_dealloc(cfg_writer_lock);
+               cfg_writer_lock = 0;
+       }
+}
+
+/* per-child process init function */
+int cfg_child_init(void)
+{
+       /* set the callback list pointer to the beginning of the list */
+       cfg_child_cb = *cfg_child_cb_first;
+       atomic_inc(&cfg_child_cb->refcnt);
+
+       return 0;
+}
+
+/* searches a variable definition by group and variable name */
+int cfg_lookup_var(str *gname, str *vname,
+                       cfg_group_t **group, cfg_mapping_t **var)
+{
+       cfg_group_t     *g;
+       int             i;
+
+       for (   g = cfg_group;
+               g;
+               g = g->next
+       )
+               if ((g->name_len == gname->len)
+               && (memcmp(g->name, gname->s, gname->len)==0)) {
+
+                       for (   i = 0;
+                               i < g->size;
+                               i++
+                       ) {
+                               if ((g->mapping[i].name_len == vname->len)
+                               && (memcmp(g->mapping[i].def->name, vname->s, vname->len)==0)) {
+                                       if (group) *group = g;
+                                       if (var) *var = &(g->mapping[i]);
+                                       return 0;
+                               }
+                       }
+                       break;
+               }
+
+       LOG(L_ERR, "ERROR: cfg_lookup_var(): variable not found: %.*s.%.*s\n",
+                       gname->len, gname->s,
+                       vname->len, vname->s);
+       return -1;
+}
+
+/* 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     *block;
+
+       block = (cfg_block_t*)shm_malloc(sizeof(cfg_block_t)+cfg_block_size-1);
+       if (!block) {
+               LOG(L_ERR, "ERROR: cfg_clone_global(): not enough shm memory\n");
+               return NULL;
+       }
+       memcpy(block, *cfg_global, sizeof(cfg_block_t)+cfg_block_size-1);
+
+       /* reset the reference counter */
+       atomic_set(&block->refcnt, 0);
+
+       return block;
+}
+
+/* installs a new global config
+ *
+ * replaced is an array of strings that must be freed together
+ * with the previous global config.
+ * cb_first and cb_last define a linked list of per-child process
+ * callbacks. This list is added to the global linked list.
+ */
+void cfg_install_global(cfg_block_t *block, char **replaced,
+                       cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last)
+{
+       CFG_LOCK();
+
+       if (*cfg_global) {
+               if (replaced) (*cfg_global)->replaced = replaced;
+               CFG_UNREF(*cfg_global);
+       }
+       CFG_REF(block);
+       *cfg_global = block;
+
+       if (cb_first) {
+               /* add the new callbacks to the end of the linked-list */
+               (*cfg_child_cb_last)->next = cb_first;
+               *cfg_child_cb_last = cb_last;
+       }
+
+       CFG_UNLOCK();
+
+}
+
+/* creates a structure for a per-child process callback */
+cfg_child_cb_t *cfg_child_cb_new(str *name, cfg_on_set_child cb)
+{
+       cfg_child_cb_t  *cb_struct;
+
+       cb_struct = (cfg_child_cb_t *)shm_malloc(sizeof(cfg_child_cb_t));
+       if (!cb_struct) {
+               LOG(L_ERR, "ERROR: cfg_child_cb_new(): not enough shm memory\n");
+               return NULL;
+       }
+       memset(cb_struct, 0, sizeof(cfg_child_cb_t));
+       if (name) {
+               cb_struct->name.s = name->s;
+               cb_struct->name.len = name->len;
+       }
+       cb_struct->cb = cb;
+       atomic_set(&cb_struct->refcnt, 0);
+
+       return cb_struct;
+}
+
+/* free the memory allocated for a child cb list */
+void cfg_child_cb_free(cfg_child_cb_t *child_cb_first)
+{
+       cfg_child_cb_t  *cb, *cb_next;
+
+       for(    cb = child_cb_first;
+               cb;
+               cb = cb_next
+       ) {
+               cb_next = cb->next;
+               shm_free(cb);
+       }
+}
diff --git a/cfg/cfg_struct.h b/cfg/cfg_struct.h
new file mode 100644 (file)
index 0000000..e9554ad
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2007 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    info@iptel.org
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History
+ * -------
+ *  2007-12-03 Initial version (Miklos)
+ */
+
+#ifndef _CFG_STRUCT_H
+#define _CFG_STRUCT_H
+
+#include "../str.h"
+#include "../atomic_ops.h"
+#include "../mem/shm_mem.h"
+#include "../locking.h"
+#include "../compiler_opt.h"
+#include "cfg.h"
+
+/* indicates that the variable has been already shmized */
+#define cfg_var_shmized        1U
+
+/* structure used for variable - pointer mapping */
+typedef struct _cfg_mapping {
+       cfg_def_t       *def;           /* one item of the cfg structure definition */
+       int             name_len;       /* length of def->name */
+
+       /* additional information about the cfg variable */
+       int             offset; /* offest within the memory block */
+       unsigned int    flag;   /* flag indicating the state of the variable */
+} cfg_mapping_t;
+
+/* linked list of registered groups */
+typedef struct _cfg_group {
+       int             num;            /* number of variables within the group */
+       cfg_mapping_t   *mapping;       /* describes the mapping betweeen
+                                       the cfg variable definition and the memory block */
+       char            *vars;          /* pointer to the memory block where the values
+                                       are stored -- used only before the config is
+                                       shmized. */
+       int             size;           /* size of the memory block that has to be
+                                       allocated to store the values */
+       int             offset;         /* offset of the group within the
+                                       shmized memory block */
+       void            **handle;       /* per-process handle that can be used
+                                       by the modules to access the variables.
+                                       It is registered when the group is created,
+                                       and updated every time the block is replaced */
+
+       struct _cfg_group       *next;
+       int             name_len;       
+       char            name[1];
+} cfg_group_t;
+
+/* single memoy block that contains all the cfg values */
+typedef struct _cfg_block {
+       atomic_t        refcnt;         /* reference counter,
+                                       the block is automatically deleted
+                                       when it reaches 0 */
+       char            **replaced;     /* set of the strings that must be freed
+                                       together with the block. The content depends
+                                       on the block that replaces this one */
+       unsigned char   vars[1];        /* blob that contains the values */
+} cfg_block_t;
+
+/* Linked list of per-child process callbacks.
+ * Each child process has a local pointer, and executes the callbacks
+ * when the pointer is not pointing to the end of the list.
+ * Items from the begginning of the list are deleted when the starter
+ * pointer is moved, and no more child process uses them.
+ */
+typedef struct _cfg_child_cb {
+       atomic_t                refcnt; /* number of child processes
+                                       referring to the element */
+       str                     name;   /* name of the variable that has changed */
+       cfg_on_set_child        cb;     /* callback function that has to be called */
+
+       struct _cfg_child_cb    *next;
+} cfg_child_cb_t;
+
+extern cfg_group_t     *cfg_group;
+extern cfg_block_t     **cfg_global;
+extern cfg_block_t     *cfg_local;
+extern gen_lock_t      *cfg_global_lock;
+extern gen_lock_t      *cfg_writer_lock;
+extern int             cfg_shmized;
+extern cfg_child_cb_t  **cfg_child_cb_first;
+extern cfg_child_cb_t  **cfg_child_cb_last;
+extern cfg_child_cb_t  *cfg_child_cb;
+
+/* macros for easier variable access */
+#define CFG_VAR_TYPE(var)      CFG_VAR_MASK((var)->def->type)
+#define CFG_INPUT_TYPE(var)    CFG_INPUT_MASK((var)->def->type)
+
+/* initiate the cfg framework */
+int cfg_init(void);
+
+/* destroy the memory allocated for the cfg framework */
+void cfg_destroy(void);
+
+/* per-child process init function */
+int cfg_child_init(void);
+
+/* creates a new cfg group, and adds it to the linked list */
+int cfg_new_group(char *name, int num, cfg_mapping_t *mapping,
+               char *vars, int size, void **handle);
+
+/* copy the variables to shm mem */
+int cfg_shmize(void);
+
+/* free the memory of a config block */
+static inline void cfg_block_free(cfg_block_t *block)
+{
+       int     i;
+
+       /* free the changed variables */
+       if (block->replaced) {
+               for (i=0; block->replaced[i]; i++)
+                       shm_free(block->replaced[i]);
+               shm_free(block->replaced);
+       }
+       shm_free(block);
+}
+
+/* lock and unlock the global cfg block -- used only at the
+ * very last step when the block is replaced */
+#define CFG_LOCK()     lock_get(cfg_global_lock);
+#define CFG_UNLOCK()   lock_release(cfg_global_lock);
+
+/* lock and unlock used by the cfg drivers to make sure that
+ * only one driver process is considering replacing the global
+ * cfg block */
+#define CFG_WRITER_LOCK()      lock_get(cfg_writer_lock);
+#define CFG_WRITER_UNLOCK()    lock_release(cfg_writer_lock);
+
+/* increase and decrease the reference counter of a block */
+#define CFG_REF(block) \
+       atomic_inc(&(block)->refcnt)
+
+#define CFG_UNREF(block) \
+       do { \
+               if (atomic_dec_and_test(&(block)->refcnt)) \
+                       cfg_block_free(block); \
+       } while(0)
+
+/* updates all the module handles and calls the
+ * per-child process callbacks -- not intended to be used
+ * directly, use cfg_update() instead!
+ */
+static inline void cfg_update_local(void)
+{
+       cfg_group_t     *group;
+       cfg_child_cb_t  *last_cb;
+       cfg_child_cb_t  *prev_cb;
+
+       if (cfg_local) CFG_UNREF(cfg_local);
+       CFG_LOCK();
+       CFG_REF(*cfg_global);
+       cfg_local = *cfg_global;
+       /* the value of the last callback must be read within the lock */
+       last_cb = *cfg_child_cb_last;
+
+       /* I unlock now, because the child process can update its own private
+       config without the lock held. In the worst case, the process will get the
+       lock once more to set cfg_child_cb_first, but only one of the child
+       processes will do so, and only if a value, that has per-child process
+       callback defined, was changed. */
+       CFG_UNLOCK();
+
+       /* update the handles */
+       for (   group = cfg_group;
+               group;
+               group = group->next
+       )
+               *(group->handle) = cfg_local->vars + group->offset;
+
+       /* call the per-process callbacks */
+       while (cfg_child_cb != last_cb) {
+               prev_cb = cfg_child_cb;
+               cfg_child_cb = cfg_child_cb->next;
+               atomic_inc(&cfg_child_cb->refcnt);
+               if (atomic_dec_and_test(&prev_cb->refcnt)) {
+                       /* No more pocess refers to this callback.
+                       Did this process block the deletion,
+                       or is there any other process that has not
+                       reached prev_cb yet? */
+                       CFG_LOCK();
+                       if (*cfg_child_cb_first == prev_cb) {
+                               /* yes, this process was blocking the deletion */
+                               *cfg_child_cb_first = cfg_child_cb;
+                               CFG_UNLOCK();
+                               shm_free(prev_cb);
+                       } else {
+                               CFG_UNLOCK();
+                       }
+               }
+               /* execute the callback */
+               cfg_child_cb->cb(&cfg_child_cb->name);
+       }
+}
+
+/* sets the local cfg block to the active block
+ * 
+ * If your module forks a new process that implements
+ * an infinite loop, put cfg_update() to the beginning of
+ * the cycle to make sure, that subsequent function calls see the
+ * up-to-date config set.
+ */
+#define cfg_update() \
+       do { \
+               if (unlikely(cfg_local != *cfg_global)) \
+                       cfg_update_local(); \
+       } while(0)
+       
+/* searches a variable definition by group and variable name */
+int cfg_lookup_var(str *gname, str *vname,
+                       cfg_group_t **group, cfg_mapping_t **var);
+
+/* clones the global config block */
+cfg_block_t *cfg_clone_global(void);
+
+/* clones a string to shared memory */
+char *cfg_clone_str(str s);
+
+/* installs a new global config
+ *
+ * replaced is an array of strings that must be freed together
+ * with the previous global config.
+ * cb_first and cb_last define a linked list of per-child process
+ * callbacks. This list is added to the global linked list.
+ */
+void cfg_install_global(cfg_block_t *block, char **replaced,
+                       cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last);
+
+/* creates a structure for a per-child process callback */
+cfg_child_cb_t *cfg_child_cb_new(str *name, cfg_on_set_child cb);
+
+/* free the memory allocated for a child cb list */
+void cfg_child_cb_free(cfg_child_cb_t *child_cb_first);
+
+#endif /* _CFG_STRUCT_H */