--- /dev/null
+/*
+ * $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;
+}
--- /dev/null
+/*
+ * $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 */
--- /dev/null
+/*
+ * $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;
+}
--- /dev/null
+/*
+ * $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 */
--- /dev/null
+/*
+ * $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);
+ }
+}
--- /dev/null
+/*
+ * $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 */