/* 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, void **replaced, cfg_child_cb_t *cb_first, cfg_child_cb_t *cb_last) { cfg_block_t* old_cfg; CFG_REF(block); if (replaced) { /* The replaced array is specified, it has to be linked to the child cb structure. * The last child process processing this structure will free the old strings and the array. */ if (cb_first) { cb_first->replaced = replaced; } else { /* At least one child cb structure is needed. */ cb_first = cfg_child_cb_new(NULL, NULL, NULL, 0 /* gname, name, cb, type */); if (cb_first) { cb_last = cb_first; cb_first->replaced = replaced; } else { LOG(L_ERR, "ERROR: cfg_install_global(): not enough shm memory\n"); /* Nothing more can be done here, the replaced strings are still needed, * they cannot be freed at this moment. */ } } } CFG_LOCK(); old_cfg = *cfg_global; *cfg_global = block; if (cb_first) cfg_install_child_cb(cb_first, cb_last); CFG_UNLOCK(); if (old_cfg) CFG_UNREF(old_cfg); }
/* initiate the cfg framework */ int sr_cfg_init(void) { cfg_global_lock = lock_alloc(); if (!cfg_global_lock) { LOG(L_ERR, "ERROR: sr_cfg_init(): not enough shm memory\n"); goto error; } if (lock_init(cfg_global_lock) == 0) { LOG(L_ERR, "ERROR: sr_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: sr_cfg_init(): not enough shm memory\n"); goto error; } if (lock_init(cfg_writer_lock) == 0) { LOG(L_ERR, "ERROR: sr_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: sr_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: sr_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: sr_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, NULL, 0); if (!*cfg_child_cb_first) goto error; return 0; error: cfg_destroy(); return -1; }
/* 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, s2; 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->group->name; s.len = changed->group->name_len; s2.s = changed->var->def->name; s2.len = changed->var->name_len; child_cb = cfg_child_cb_new(&s, &s2, changed->var->def->on_set_child_cb, changed->var->def->type); 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; } } if (replaced_num) { /* 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)) { replaced[replaced_num] = *(char **)p; if (replaced[replaced_num]) replaced_num++; /* else do not increase replaced_num, because the cfg_block_free() will stop at the first NULL value */ } memcpy( p, changed->new_val.vraw, 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; }
/* sets the value of a variable without the need of commit * * return value: * 0: success * -1: error * 1: variable has not been found */ 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, s2; char *old_string = NULL; char **replaced = NULL; cfg_child_cb_t *child_cb = NULL; /* 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 the variable is read-only */ if (var->def->type & CFG_READONLY) { LOG(L_ERR, "ERROR: cfg_set_now(): variable is read-only\n"); goto error0; } /* check whether we have to convert the type */ if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v)) goto error0; if ((CFG_INPUT_TYPE(var) == CFG_INPUT_INT) && (var->def->min || var->def->max)) { /* perform a simple min-max check for integers */ if (((int)(long)v < var->def->min) || ((int)(long)v > var->def->max)) { LOG(L_ERR, "ERROR: cfg_set_now(): integer value is out of range\n"); 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), group_name, var_name, &v) < 0) { LOG(L_ERR, "ERROR: cfg_set_now(): fixup failed\n"); goto error0; } } if (var->def->on_set_child_cb) { /* get the name of the variable from the internal struct, because var_name may be freed before the callback needs it */ s.s = group->name; s.len = group->name_len; s2.s = var->def->name; s2.len = var->name_len; child_cb = cfg_child_cb_new(&s, &s2, var->def->on_set_child_cb, var->def->type); if (!child_cb) { LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n"); goto error0; } } if (cfg_shmized) { /* make sure that nobody else replaces the global config while the new one is prepared */ CFG_WRITER_LOCK(); if (var->def->type & CFG_ATOMIC) { /* atomic change is allowed, we can rewrite the value directly in the global config */ p = (*cfg_global)->vars+group->offset+var->offset; } else { /* 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 The handle either points to group->vars, or to the shared memory block (dynamic group) */ p = *(group->handle) + var->offset; } /* set the new value */ switch (CFG_VAR_TYPE(var)) { case CFG_VAR_INT: *(int *)p = (int)(long)v; break; case CFG_VAR_STRING: /* clone the string to shm mem */ s.s = v; s.len = (s.s) ? strlen(s.s) : 0; if (cfg_clone_str(&s, &s)) goto error; old_string = *(char **)p; *(char **)p = s.s; break; case CFG_VAR_STR: /* clone the string to shm mem */ s = *(str *)v; if (cfg_clone_str(&s, &s)) goto error; old_string = *(char **)p; memcpy(p, &s, sizeof(str)); break; case CFG_VAR_POINTER: *(void **)p = v; break; } if (cfg_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 */ if (block) cfg_install_global(block, replaced, child_cb, child_cb); CFG_WRITER_UNLOCK(); } else { /* cfg_set() may be called more than once before forking */ if (old_string && (var->flag & cfg_var_shmized)) shm_free(old_string); /* flag the variable because there is no need to shmize it again */ var->flag |= cfg_var_shmized; /* the global config does not have to be replaced, but the child callback has to be installed, otherwise the child processes will miss the change */ if (child_cb) cfg_install_child_cb(child_cb, child_cb); } 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 if (val_type == CFG_VAR_STRING) 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); else /* str type */ 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, ((str *)val)->len, ((str *)val)->s); convert_val_cleanup(); 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); convert_val_cleanup(); return -1; }