Implementing a configuration framework, that can be used by SER core,
[sip-router] / cfg / cfg_ctx.c
1 /*
2  * $Id$
3  *
4  * Copyright (C) 2007 iptelorg GmbH
5  *
6  * This file is part of ser, a free SIP server.
7  *
8  * ser is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version
12  *
13  * For a license to use the ser software under conditions
14  * other than those described here, or to purchase support for this
15  * software, please contact iptel.org by e-mail at the following addresses:
16  *    info@iptel.org
17  *
18  * ser is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  *
27  * History
28  * -------
29  *  2007-12-03  Initial version (Miklos)
30  */
31
32 #include <string.h>
33
34 #include "cfg_struct.h"
35 #include "cfg_ctx.h"
36
37 /* linked list of all the registered cfg contexts */
38 static cfg_ctx_t        *cfg_ctx_list = NULL;
39
40 /* creates a new config context that is an interface to the
41  * cfg variables with write permission
42  */
43 cfg_ctx_t *cfg_register_ctx(cfg_on_declare on_declare_cb)
44 {
45         cfg_ctx_t       *ctx;
46         cfg_group_t     *group;
47         str             gname;
48
49         /* allocate memory for the new context
50         Better to use shm mem, because 'changed' and 'lock'
51         must be in shm mem anyway */
52         ctx = (cfg_ctx_t *)shm_malloc(sizeof(cfg_ctx_t));
53         if (!ctx) {
54                 LOG(L_ERR, "ERROR: cfg_register_ctx(): not enough shm memory\n");
55                 return NULL;
56         }
57         memset(ctx, 0, sizeof(cfg_ctx_t));
58         if (lock_init(&ctx->lock) == 0) {
59                 LOG(L_ERR, "ERROR: cfg_register_ctx(): failed to init lock\n");
60                 shm_free(ctx);
61                 return NULL;
62         }
63
64         /* add the new ctx to the beginning of the list */
65         ctx->next = cfg_ctx_list;
66         cfg_ctx_list = ctx;
67
68         /* let the driver know about the already registered groups */
69         if (on_declare_cb) {
70                 ctx->on_declare_cb = on_declare_cb;
71
72                 for (   group = cfg_group;
73                         group;
74                         group = group->next
75                 ) {
76                         gname.s = group->name;
77                         gname.len = group->name_len;
78                         on_declare_cb(&gname, group->mapping->def);
79                 }
80         }
81
82         return ctx;
83 }
84
85 /* free the memory allocated for the contexts */
86 void cfg_ctx_destroy(void)
87 {
88         cfg_ctx_t       *ctx, *ctx2;
89
90         for (   ctx = cfg_ctx_list;
91                 ctx;
92                 ctx = ctx2
93         ) {
94                 ctx2 = ctx->next;
95                 shm_free(ctx);
96         }
97         cfg_ctx_list = NULL;
98 }
99
100 /* notify the drivers about the new config definition */
101 void cfg_notify_drivers(char *group_name, cfg_def_t *def)
102 {
103         cfg_ctx_t       *ctx;
104         str             gname;
105
106         gname.s = group_name;
107         gname.len = strlen(group_name);
108
109         for (   ctx = cfg_ctx_list;
110                 ctx;
111                 ctx = ctx->next
112         )
113                 if (ctx->on_declare_cb)
114                         ctx->on_declare_cb(&gname, def);
115 }
116
117 /* convert the value to the requested type
118  * (only string->str is implemented currently) */
119 static int convert_val(unsigned int val_type, void *val,
120                         unsigned int var_type, void **new_val)
121 {
122         static str      s;
123
124         switch (val_type) {
125                 case CFG_VAR_INT:
126                         if (CFG_INPUT_MASK(var_type) != CFG_INPUT_INT)
127                                 goto error;
128                         *new_val = val;
129                         break;
130
131                 case CFG_VAR_STRING:
132                         if (CFG_INPUT_MASK(var_type) == CFG_INPUT_STR) {
133                                 s.s = val;
134                                 s.len = strlen(s.s);
135                                 *new_val = (void *)&s;
136                                 break;
137                         }
138                         if (CFG_INPUT_MASK(var_type) != CFG_INPUT_STRING)
139                                 goto error;
140                         *new_val = val;
141                         break;
142                 default:
143                         goto error;
144         }
145
146         return 0;
147
148 error:
149         LOG(L_ERR, "ERROR: convert_val(): got a value with type %u, but expected %u\n",
150                         val_type, CFG_INPUT_MASK(var_type));
151         return -1;
152 }
153
154 /* sets the value of a variable without the need of commit */
155 int cfg_set_now(cfg_ctx_t *ctx, str *group_name, str *var_name,
156                         void *val, unsigned int val_type)
157 {
158         cfg_group_t     *group;
159         cfg_mapping_t   *var;
160         void            *p, *v;
161         cfg_block_t     *block = NULL;
162         str             s;
163         char            *old_string = NULL;
164         char            **replaced = NULL;
165         cfg_child_cb_t  *child_cb = NULL;
166         int             i;
167
168         /* verify the context even if we do not need it now
169         to make sure that a cfg driver has called the function
170         (very very weak security) */
171         if (!ctx) {
172                 LOG(L_ERR, "ERROR: cfg_set_now(): context is undefined\n");
173                 return -1;
174         }
175
176         /* look-up the group and the variable */
177         if (cfg_lookup_var(group_name, var_name, &group, &var))
178                 return -1;
179
180         /* check whether we have to convert the type */
181         if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
182                 goto error0;
183         
184         if (var->def->on_change_cb) {
185                 /* Call the fixup function.
186                 There is no need to set a temporary cfg handle,
187                 becaue a single variable is changed */
188                 if (var->def->on_change_cb(*(group->handle),
189                                                 var_name,
190                                                 &v) < 0) {
191                         LOG(L_ERR, "ERROR: cfg_set_now(): fixup failed\n");
192                         goto error0;
193                 }
194
195         } else if ((CFG_VAR_TYPE(var) == CFG_VAR_INT) 
196         && (var->def->min != var->def->max)) {
197                 /* perform a simple min-max check for integers */
198                 if (((int)(long)v < var->def->min)
199                 || ((int)(long)v > var->def->max)) {
200                         LOG(L_ERR, "ERROR: cfg_set_now(): integer value is out of range\n");
201                         goto error0;
202                 }
203         }
204
205         if (cfg_shmized) {
206                 if (var->def->on_set_child_cb) {
207                         child_cb = cfg_child_cb_new(var_name,
208                                                 var->def->on_set_child_cb);
209                         if (!child_cb) {
210                                 LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
211                                 goto error0;
212                         }
213                 }
214
215                 /* make sure that nobody else replaces the global config
216                 while the new one is prepared */
217                 CFG_WRITER_LOCK();
218
219                 /* clone the memory block, and prepare the modification */
220                 if (!(block = cfg_clone_global())) goto error;
221
222                 p = block->vars+group->offset+var->offset;
223         } else {
224                 /* we are allowed to rewrite the value on-the-fly */
225                 p = group->vars + var->offset;
226         }
227
228         /* set the new value */
229         switch (CFG_VAR_TYPE(var)) {
230         case CFG_VAR_INT:
231                 i = (int)(long)v;
232                 memcpy(p, &i, sizeof(int));
233                 break;
234
235         case CFG_VAR_STRING:
236                 /* clone the string to shm mem */
237                 s.s = v;
238                 s.len = strlen(v);
239                 if (!(s.s = cfg_clone_str(s))) goto error;
240                 memcpy(&old_string, p, sizeof(char *));
241                 memcpy(p, &s.s, sizeof(char *));
242                 break;
243
244         case CFG_VAR_STR:
245                 /* clone the string to shm mem */
246                 s = *(str *)v;
247                 if (!(s.s = cfg_clone_str(s))) goto error;
248                 memcpy(&old_string, p, sizeof(char *));
249                 memcpy(p, &s, sizeof(str));
250                 break;
251
252         case CFG_VAR_POINTER:
253                 memcpy(p, &v, sizeof(void *));
254                 break;
255
256         }
257
258         if (cfg_shmized) {
259                 if (old_string) {
260                         /* prepare the array of the replaced strings,
261                         they will be freed when the old block is freed */
262                         replaced = (char **)shm_malloc(sizeof(char *)*2);
263                         if (!replaced) {
264                                 LOG(L_ERR, "ERROR: cfg_set_now(): not enough shm memory\n");
265                                 goto error;
266                         }
267                         replaced[0] = old_string;
268                         replaced[1] = NULL;
269                 }
270                 /* replace the global config with the new one */
271                 cfg_install_global(block, replaced, child_cb, child_cb);
272                 CFG_WRITER_UNLOCK();
273         } else {
274                 /* flag the variable because there is no need
275                 to shmize it again */
276                 var->flag |= cfg_var_shmized;
277         }
278
279         if (val_type == CFG_VAR_INT)
280                 LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
281                         "has been changed to %d\n",
282                         group_name->len, group_name->s,
283                         var_name->len, var_name->s,
284                         (int)(long)val);
285         else
286                 LOG(L_INFO, "INFO: cfg_set_now(): %.*s.%.*s "
287                         "has been changed to \"%s\"\n",
288                         group_name->len, group_name->s,
289                         var_name->len, var_name->s,
290                         (char *)val);
291
292         return 0;
293
294 error:
295         if (cfg_shmized) CFG_WRITER_UNLOCK();
296         if (block) cfg_block_free(block);
297         if (child_cb) cfg_child_cb_free(child_cb);
298
299 error0:
300         LOG(L_ERR, "ERROR: cfg_set_now(): failed to set the variable: %.*s.%.*s\n",
301                         group_name->len, group_name->s,
302                         var_name->len, var_name->s);
303
304
305         return -1;
306 }
307
308 /* wrapper function for cfg_set_now */
309 int cfg_set_now_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
310 {
311         return cfg_set_now(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
312 }
313
314 /* wrapper function for cfg_set_now */
315 int cfg_set_now_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
316 {
317         return cfg_set_now(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
318 }
319
320 /* returns the size of the variable */
321 static int cfg_var_size(cfg_mapping_t *var)
322 {
323         switch (CFG_VAR_TYPE(var)) {
324
325         case CFG_VAR_INT:
326                 return sizeof(int);
327
328         case CFG_VAR_STRING:
329                 return sizeof(char *);
330
331         case CFG_VAR_STR:
332                 return sizeof(str);
333
334         case CFG_VAR_POINTER:
335                 return sizeof(void *);
336
337         default:
338                 LOG(L_CRIT, "BUG: cfg_var_sizeK(): unknown type: %u\n",
339                         CFG_VAR_TYPE(var));
340                 return 0;
341         }
342 }
343
344 /* sets the value of a variable but does not commit the change */
345 int cfg_set_delayed(cfg_ctx_t *ctx, str *group_name, str *var_name,
346                         void *val, unsigned int val_type)
347 {
348         cfg_group_t     *group;
349         cfg_mapping_t   *var;
350         void            *v;
351         char            *temp_handle;
352         int             temp_handle_created;
353         cfg_changed_var_t       *changed = NULL;
354         int             i, size;
355         str             s;
356
357         if (!cfg_shmized)
358                 /* the cfg has not been shmized yet, there is no
359                 point in registering the change and committing it later */
360                 return cfg_set_now(ctx, group_name, var_name,
361                                         val, val_type);
362
363         if (!ctx) {
364                 LOG(L_ERR, "ERROR: cfg_set_delayed(): context is undefined\n");
365                 return -1;
366         }
367
368         /* look-up the group and the variable */
369         if (cfg_lookup_var(group_name, var_name, &group, &var))
370                 return -1;
371
372         /* check whether we have to convert the type */
373         if (convert_val(val_type, val, CFG_INPUT_TYPE(var), &v))
374                 goto error0;
375
376         /* the ctx must be locked while reading and writing
377         the list of changed variables */
378         CFG_CTX_LOCK(ctx);
379
380         if (var->def->on_change_cb) {
381                 /* The fixup function must see also the
382                 not yet committed values, so a temporary handle
383                 must be prepared that points to the new config.
384                 Only the values within the group are applied,
385                 other modifications are not visible to the callback.
386                 The local config is the base. */
387
388                 if (ctx->changed_first) {
389                         temp_handle = (char *)pkg_malloc(group->size);
390                         if (!temp_handle) {
391                                 LOG(L_ERR, "ERROR: cfg_set_delayed(): "
392                                         "not enough memory\n");
393                                 goto error;
394                         }
395                         temp_handle_created = 1;
396                         memcpy(temp_handle, *(group->handle), group->size);
397
398                         /* apply the changes */
399                         for (   changed = ctx->changed_first;
400                                 changed;
401                                 changed = changed->next
402                         ) {
403                                 if (changed->group != group) continue;
404
405                                 memcpy( temp_handle + changed->var->offset,
406                                         changed->new_val,
407                                         cfg_var_size(changed->var));
408                         }
409                 } else {
410                         /* there is not any change */
411                         temp_handle = *(group->handle);
412                         temp_handle_created = 0;
413                 }
414                         
415                 if (var->def->on_change_cb(temp_handle,
416                                                 var_name,
417                                                 &v) < 0) {
418                         LOG(L_ERR, "ERROR: cfg_set_delayed(): fixup failed\n");
419                         if (temp_handle_created) pkg_free(temp_handle);
420                         goto error;
421                 }
422                 if (temp_handle_created) pkg_free(temp_handle);
423
424         } else if ((CFG_VAR_TYPE(var) == CFG_VAR_INT) 
425         && (var->def->min != var->def->max)) {
426                 /* perform a simple min-max check for integers */
427                 if (((int)(long)v < var->def->min)
428                 || ((int)(long)v > var->def->max)) {
429                         LOG(L_ERR, "ERROR: cfg_set_delayed(): integer value is out of range\n");
430                         goto error;
431                 }
432         }
433
434         /* everything went ok, we can add the new value to the list */
435         size = sizeof(cfg_changed_var_t) + cfg_var_size(var) - 1;
436         changed = (cfg_changed_var_t *)shm_malloc(size);
437         if (!changed) {
438                 LOG(L_ERR, "ERROR: cfg_set_delayed(): not enough shm memory\n");
439                 goto error;
440         }
441         memset(changed, 0, size);
442         changed->group = group;
443         changed->var = var;
444
445         switch (CFG_VAR_TYPE(var)) {
446
447         case CFG_VAR_INT:
448                 i = (int)(long)v;
449                 memcpy(changed->new_val, &i, sizeof(int));
450                 break;
451
452         case CFG_VAR_STRING:
453                 /* clone the string to shm mem */
454                 s.s = v;
455                 s.len = strlen(v);
456                 if (!(s.s = cfg_clone_str(s))) goto error;
457                 memcpy(changed->new_val, &s.s, sizeof(char *));
458                 break;
459
460         case CFG_VAR_STR:
461                 /* clone the string to shm mem */
462                 s = *(str *)v;
463                 if (!(s.s = cfg_clone_str(s))) goto error;
464                 memcpy(changed->new_val, &s, sizeof(str));
465                 break;
466
467         case CFG_VAR_POINTER:
468                 memcpy(changed->new_val, &v, sizeof(void *));
469                 break;
470
471         }
472
473         /* Add the new item to the end of the linked list,
474         The commit will go though the list from the first item,
475         so the list is kept in order */
476         if (ctx->changed_first)
477                 ctx->changed_last->next = changed;
478         else
479                 ctx->changed_first = changed;
480
481         ctx->changed_last = changed;
482
483         CFG_CTX_UNLOCK(ctx);
484
485         if (val_type == CFG_VAR_INT)
486                 LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
487                         "is going to be changed to %d "
488                         "[context=%p]\n",
489                         group_name->len, group_name->s,
490                         var_name->len, var_name->s,
491                         (int)(long)val,
492                         ctx);
493         else
494                 LOG(L_INFO, "INFO: cfg_set_delayed(): %.*s.%.*s "
495                         "is going to be changed to \"%s\" "
496                         "[context=%p]\n",
497                         group_name->len, group_name->s,
498                         var_name->len, var_name->s,
499                         (char *)val,
500                         ctx);
501
502         return 0;
503
504 error:
505         CFG_CTX_UNLOCK(ctx);
506         if (changed) shm_free(changed);
507 error0:
508         LOG(L_ERR, "ERROR: cfg_set_delayed(): failed to set the variable: %.*s.%.*s\n",
509                         group_name->len, group_name->s,
510                         var_name->len, var_name->s);
511
512         return -1;
513 }
514
515 /* wrapper function for cfg_set_delayed */
516 int cfg_set_delayed_int(cfg_ctx_t *ctx, str *group_name, str *var_name, int val)
517 {
518         return cfg_set_delayed(ctx, group_name, var_name, (void *)(long)val, CFG_VAR_INT);
519 }
520
521 /* wrapper function for cfg_set_delayed */
522 int cfg_set_delayed_string(cfg_ctx_t *ctx, str *group_name, str *var_name, char *val)
523 {
524         return cfg_set_delayed(ctx, group_name, var_name, (void *)val, CFG_VAR_STRING);
525 }
526
527 /* commits the previously prepared changes within the context */
528 int cfg_commit(cfg_ctx_t *ctx)
529 {
530         int     replaced_num = 0;
531         cfg_changed_var_t       *changed, *changed2;
532         cfg_block_t     *block;
533         char    **replaced = NULL;
534         cfg_child_cb_t  *child_cb;
535         cfg_child_cb_t  *child_cb_first = NULL;
536         cfg_child_cb_t  *child_cb_last = NULL;
537         int     size;
538         void    *p;
539         str     s;
540
541         if (!ctx) {
542                 LOG(L_ERR, "ERROR: cfg_commit(): context is undefined\n");
543                 return -1;
544         }
545
546         if (!cfg_shmized) return 0; /* nothing to do */
547
548         /* the ctx must be locked while reading and writing
549         the list of changed variables */
550         CFG_CTX_LOCK(ctx);
551
552         /* is there any change? */
553         if (!ctx->changed_first) goto done;
554
555         /* count the number of replaced strings,
556         and prepare the linked list of per-child process
557         callbacks, that will be added to the global list */
558         for (   changed = ctx->changed_first;
559                 changed;
560                 changed = changed->next
561         ) {
562                 if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
563                 || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR))
564                         replaced_num++;
565
566
567                 if (changed->var->def->on_set_child_cb) {
568                         s.s = changed->var->def->name;
569                         s.len = changed->var->name_len;
570                         child_cb = cfg_child_cb_new(&s,
571                                         changed->var->def->on_set_child_cb);
572                         if (!child_cb) goto error0;
573
574                         if (child_cb_last)
575                                 child_cb_last->next = child_cb;
576                         else
577                                 child_cb_first = child_cb;
578                         child_cb_last = child_cb;
579                 }
580         }
581
582         /* allocate memory for the replaced string array */
583         size = sizeof(char *)*(replaced_num + 1);
584         replaced = (char **)shm_malloc(size);
585         if (!replaced) {
586                 LOG(L_ERR, "ERROR: cfg_commit(): not enough shm memory\n");
587                 goto error;
588         }
589         memset(replaced, 0 , size);
590
591         /* make sure that nobody else replaces the global config
592         while the new one is prepared */
593         CFG_WRITER_LOCK();
594
595         /* clone the memory block, and prepare the modification */
596         if (!(block = cfg_clone_global())) {
597                 CFG_WRITER_UNLOCK();
598                 goto error;
599         }
600
601         /* apply the modifications to the buffer */
602         replaced_num = 0;
603         for (   changed = ctx->changed_first;
604                 changed;
605                 changed = changed->next
606         ) {
607                 p = block->vars
608                         + changed->group->offset
609                         + changed->var->offset;
610
611                 if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
612                 || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) {
613                         memcpy(&(replaced[replaced_num]), p, sizeof(char *));
614                         replaced_num++;
615                 }
616
617                 memcpy( p,
618                         changed->new_val,
619                         cfg_var_size(changed->var));
620         }
621
622         /* replace the global config with the new one */
623         cfg_install_global(block, replaced, child_cb_first, child_cb_last);
624         CFG_WRITER_UNLOCK();
625
626         /* free the changed list */     
627         for (   changed = ctx->changed_first;
628                 changed;
629                 changed = changed2
630         ) {
631                 changed2 = changed->next;
632                 shm_free(changed);
633         }
634         ctx->changed_first = NULL;
635         ctx->changed_last = NULL;
636
637 done:
638         LOG(L_INFO, "INFO: cfg_commit(): config changes have been applied "
639                         "[context=%p]\n",
640                         ctx);
641
642         CFG_CTX_UNLOCK(ctx);
643         return 0;
644
645 error:
646         CFG_CTX_UNLOCK(ctx);
647
648 error0:
649
650         if (child_cb_first) cfg_child_cb_free(child_cb_first);
651         if (replaced) shm_free(replaced);
652
653         return -1;
654 }
655
656 /* drops the not yet committed changes within the context */
657 int cfg_rollback(cfg_ctx_t *ctx)
658 {
659         cfg_changed_var_t       *changed, *changed2;
660         char    *new_string;
661
662         if (!ctx) {
663                 LOG(L_ERR, "ERROR: cfg_rollback(): context is undefined\n");
664                 return -1;
665         }
666
667         if (!cfg_shmized) return 0; /* nothing to do */
668
669         LOG(L_INFO, "INFO: cfg_rollback(): deleting the config changes "
670                         "[context=%p]\n",
671                         ctx);
672
673         /* the ctx must be locked while reading and writing
674         the list of changed variables */
675         CFG_CTX_LOCK(ctx);
676
677         for (   changed = ctx->changed_first;
678                 changed;
679                 changed = changed2
680         ) {
681                 changed2 = changed->next;
682
683                 if ((CFG_VAR_TYPE(changed->var) == CFG_VAR_STRING)
684                 || (CFG_VAR_TYPE(changed->var) == CFG_VAR_STR)) {
685                         memcpy(&new_string, changed->new_val, sizeof(char *));
686                         shm_free(new_string);
687                 }
688                 shm_free(changed);
689         }
690         ctx->changed_first = NULL;
691         ctx->changed_last = NULL;
692
693         CFG_CTX_UNLOCK(ctx);
694
695         return 0;
696 }
697
698 /* returns the value of a variable */
699 int cfg_get_by_name(cfg_ctx_t *ctx, str *group_name, str *var_name,
700                         void **val, unsigned int *val_type)
701 {
702         cfg_group_t     *group;
703         cfg_mapping_t   *var;
704         void            *p;
705         static str      s;      /* we need the value even
706                                 after the function returns */
707         int             i;
708         char            *ch;
709
710         /* verify the context even if we do not need it now
711         to make sure that a cfg driver has called the function
712         (very very weak security) */
713         if (!ctx) {
714                 LOG(L_ERR, "ERROR: cfg_get_by_name(): context is undefined\n");
715                 return -1;
716         }
717
718         /* look-up the group and the variable */
719         if (cfg_lookup_var(group_name, var_name, &group, &var))
720                 return -1;
721
722         /* use the module's handle to access the variable
723         It means that the variable is read from the local config
724         after forking */
725         p = *(group->handle) + var->offset;
726
727         switch (CFG_VAR_TYPE(var)) {
728         case CFG_VAR_INT:
729                 memcpy(&i, p, sizeof(int));
730                 *val = (void *)(long)i;
731                 break;
732
733         case CFG_VAR_STRING:
734                 memcpy(&ch, p, sizeof(char *));
735                 *val = (void *)ch;
736                 break;
737
738         case CFG_VAR_STR:
739                 memcpy(&s, p, sizeof(str));
740                 *val = (void *)&s;
741                 break;
742
743         case CFG_VAR_POINTER:
744                 memcpy(val, &p, sizeof(void *));
745                 break;
746
747         }
748         *val_type = CFG_VAR_TYPE(var);
749
750         return 0;
751 }
752
753 /* returns the description of a variable */
754 int cfg_help(cfg_ctx_t *ctx, str *group_name, str *var_name,
755                         char **ch)
756 {
757         cfg_mapping_t   *var;
758
759         /* verify the context even if we do not need it now
760         to make sure that a cfg driver has called the function
761         (very very weak security) */
762         if (!ctx) {
763                 LOG(L_ERR, "ERROR: cfg_help(): context is undefined\n");
764                 return -1;
765         }
766
767         /* look-up the group and the variable */
768         if (cfg_lookup_var(group_name, var_name, NULL, &var))
769                 return -1;
770
771         *ch = var->def->descr;
772         return 0;
773 }