]> mj.ucw.cz Git - libucw.git/blob - lib/conf2.c
conf2: dumper of the configuration polished; support printing names of user types
[libucw.git] / lib / conf2.c
1 /*
2  *      UCW Library -- Reading of configuration files
3  *
4  *      (c) 2001--2006 Robert Spalek <robert@ucw.cz>
5  *      (c) 2003--2006 Martin Mares <mj@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #include "lib/lib.h"
12 #include "lib/conf2.h"
13 #include "lib/mempool.h"
14 #include "lib/clists.h"
15 #include "lib/fastbuf.h"
16 #include "lib/chartype.h"
17 #include "lib/lfs.h"
18 #include "lib/stkstring.h"
19 #include "lib/binsearch.h"
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <stdarg.h>
25 #include <fcntl.h>
26 #include <getopt.h>
27
28 #define TRY(f)  do { byte *_msg = f; if (_msg) return _msg; } while (0)
29
30 /* Memory allocation */
31
32 struct mempool *cf_pool;        // current pool for loading new configuration
33 static struct old_pools {
34   struct old_pools *prev;
35   struct mempool *pool;
36 } *pools;                       // link-list of older cf_pool's
37
38 void *
39 cf_malloc(uns size)
40 {
41   return mp_alloc(cf_pool, size);
42 }
43
44 void *
45 cf_malloc_zero(uns size)
46 {
47   return mp_alloc_zero(cf_pool, size);
48 }
49
50 byte *
51 cf_strdup(byte *s)
52 {
53   return mp_strdup(cf_pool, s);
54 }
55
56 byte *
57 cf_printf(char *fmt, ...)
58 {
59   va_list args;
60   va_start(args, fmt);
61   byte *res = mp_vprintf(cf_pool, fmt, args);
62   va_end(args);
63   return res;
64 }
65
66 /* Undo journal */
67
68 uns cf_need_journal = 1;        // some programs do not need journal
69 static struct cf_journal_item {
70   struct cf_journal_item *prev;
71   byte *ptr;
72   uns len;
73   byte copy[0];
74 } *journal;
75
76 void
77 cf_journal_block(void *ptr, uns len)
78 {
79   if (!cf_need_journal)
80     return;
81   struct cf_journal_item *ji = cf_malloc(sizeof(struct cf_journal_item) + len);
82   ji->prev = journal;
83   ji->ptr = ptr;
84   ji->len = len;
85   memcpy(ji->copy, ptr, len);
86   journal = ji;
87 }
88
89 static void
90 journal_swap(void)
91   // swaps the contents of the memory and the journal, and reverses the list
92 {
93   struct cf_journal_item *curr, *prev, *next;
94   for (next=NULL, curr=journal; curr; next=curr, curr=prev)
95   {
96     prev = curr->prev;
97     curr->prev = next;
98     for (uns i=0; i<curr->len; i++)
99     {
100       byte x = curr->copy[i];
101       curr->copy[i] = curr->ptr[i];
102       curr->ptr[i] = x;
103     }
104   }
105   journal = next;
106 }
107
108 struct cf_journal_item *
109 cf_journal_new_transaction(uns new_pool)
110 {
111   if (new_pool)
112     cf_pool = mp_new(1<<10);
113   struct cf_journal_item *oldj = journal;
114   journal = NULL;
115   return oldj;
116 }
117
118 void
119 cf_journal_commit_transaction(uns new_pool, struct cf_journal_item *oldj)
120 {
121   if (new_pool)
122   {
123     struct old_pools *p = cf_malloc(sizeof(struct old_pools));
124     p->prev = pools;
125     p->pool = cf_pool;
126     pools = p;
127   }
128   if (oldj)
129   {
130     struct cf_journal_item **j = &journal;
131     while (*j)
132       j = &(*j)->prev;
133     *j = oldj;
134   }
135 }
136
137 void
138 cf_journal_rollback_transaction(uns new_pool, struct cf_journal_item *oldj)
139 {
140   if (!cf_need_journal)
141     die("Cannot rollback the configuration, because the journal is disabled.");
142   journal_swap();
143   journal = oldj;
144   if (new_pool)
145   {
146     mp_delete(cf_pool);
147     cf_pool = pools ? pools->pool : NULL;
148   }
149 }
150
151 /* Dirty sections */
152
153 struct dirty_section {
154   struct cf_section *sec;
155   void *ptr;
156 };
157 #define GBUF_TYPE       struct dirty_section
158 #define GBUF_PREFIX(x)  dirtsec_##x
159 #include "lib/gbuf.h"
160 static dirtsec_t dirty;
161 static uns dirties;
162 static uns everything_committed;                // after the 1st load, this flag is set on
163
164 static void
165 add_dirty(struct cf_section *sec, void *ptr)
166 {
167   dirtsec_grow(&dirty, dirties+1);
168   struct dirty_section *dest = dirty.ptr + dirties;
169   if (dirties && dest[-1].sec == sec && dest[-1].ptr == ptr)
170     return;
171   dest->sec = sec;
172   dest->ptr = ptr;
173   dirties++;
174 }
175
176 #define ASORT_PREFIX(x) dirtsec_##x
177 #define ASORT_KEY_TYPE  struct dirty_section
178 #define ASORT_ELT(i)    dirty.ptr[i]
179 #define ASORT_LT(x,y)   x.sec < y.sec || x.sec == y.sec && x.ptr < y.ptr
180 #include "lib/arraysort.h"
181
182 static void
183 sort_dirty(void)
184 {
185   if (dirties <= 1)
186     return;
187   dirtsec_sort(dirties);
188   struct dirty_section *read = dirty.ptr + 1, *write = dirty.ptr + 1, *limit = dirty.ptr + dirties;
189   while (read < limit) {
190     if (read->sec != read[-1].sec || read->ptr != read[-1].ptr) {
191       if (read != write)
192         *write = *read;
193       write++;
194     }
195     read++;
196   }
197   dirties = write - dirty.ptr;
198 }
199
200 /* Initialization */
201
202 #define SEC_FLAG_DYNAMIC        0x80000000      // contains a dynamic attribute
203 #define SEC_FLAG_UNKNOWN        0x40000000      // ignore unknown entriies
204 #define SEC_FLAG_NUMBER         0x0fffffff      // number of entries
205
206 static struct cf_section sections;      // root section
207
208 static struct cf_item *
209 find_subitem(struct cf_section *sec, byte *name)
210 {
211   struct cf_item *ci = sec->cfg;
212   for (; ci->cls; ci++)
213     if (!strcasecmp(ci->name, name))
214       return ci;
215   return ci;
216 }
217
218 static void
219 inspect_section(struct cf_section *sec)
220 {
221   sec->flags = 0;
222   struct cf_item *ci;
223   for (ci=sec->cfg; ci->cls; ci++)
224     if (ci->cls == CC_SECTION) {
225       inspect_section(ci->u.sec);
226       sec->flags |= ci->u.sec->flags & SEC_FLAG_DYNAMIC;
227     } else if (ci->cls == CC_LIST) {
228       inspect_section(ci->u.sec);
229       sec->flags |= SEC_FLAG_DYNAMIC;
230     } else if (ci->cls == CC_DYNAMIC || ci->cls == CC_PARSER && ci->number < 0)
231       sec->flags |= SEC_FLAG_DYNAMIC;
232   sec->flags |= ci - sec->cfg;          // record the number of entries
233 }
234
235 void
236 cf_declare_section(byte *name, struct cf_section *sec, uns allow_unknown)
237 {
238   if (!sections.cfg)
239   {
240     sections.size = 50;
241     sections.cfg = xmalloc_zero(sections.size * sizeof(struct cf_item));
242   }
243   struct cf_item *ci = find_subitem(&sections, name);
244   if (ci->cls)
245     die("Cannot register section %s twice", name);
246   ci->cls = CC_SECTION;
247   ci->name = name;
248   ci->number = 1;
249   ci->ptr = NULL;
250   ci->u.sec = sec;
251   inspect_section(sec);
252   if (allow_unknown)
253     sec->flags |= SEC_FLAG_UNKNOWN;
254   ci++;
255   if (ci - sections.cfg >= (int) sections.size)
256   {
257     sections.cfg = xrealloc(sections.cfg, 2*sections.size * sizeof(struct cf_item));
258     bzero(sections.cfg + sections.size, sections.size * sizeof(struct cf_item));
259     sections.size *= 2;
260   }
261 }
262
263 void
264 cf_init_section(byte *name, struct cf_section *sec, void *ptr, uns do_bzero)
265 {
266   if (do_bzero) {
267     ASSERT(sec->size);
268     bzero(ptr, sec->size);
269   }
270   for (uns i=0; sec->cfg[i].cls; i++)
271     if (sec->cfg[i].cls == CC_SECTION)
272       cf_init_section(sec->cfg[i].name, sec->cfg[i].u.sec, ptr + (addr_int_t) sec->cfg[i].ptr, 0);
273     else if (sec->cfg[i].cls == CC_LIST)
274       clist_init(ptr + (addr_int_t) sec->cfg[i].ptr);
275   if (sec->init) {
276     byte *msg = sec->init(ptr);
277     if (msg)
278       die("Cannot initialize section %s: %s", name, msg);
279   }
280 }
281
282 static void
283 global_init(void)
284 {
285   static uns initialized = 0;
286   if (initialized++)
287     return;
288   sections.flags |= SEC_FLAG_UNKNOWN;
289   sections.size = 0;                    // size of allocated array used to be stored here
290   cf_init_section("top-level", &sections, NULL, 0);
291 }
292
293 static int
294 commit_section(byte *name, struct cf_section *sec, void *ptr, uns commit_all)
295 {
296   struct cf_item *ci;
297   for (ci=sec->cfg; ci->cls; ci++)
298     if (ci->cls == CC_SECTION) {
299       if (commit_section(ci->name, ci->u.sec, ptr + (addr_int_t) ci->ptr, commit_all)) {
300         log(L_ERROR, "It happened in section %s", ci->name);
301         return 1;
302       }
303     } else if (ci->cls == CC_LIST) {
304       struct cnode *n;
305       uns idx = 0;
306       CLIST_WALK(n, * (clist*) (ptr + (addr_int_t) ci->ptr))
307         if (idx++, commit_section(ci->name, ci->u.sec, n, commit_all)) {
308           log(L_ERROR, "It happened in node #%d of list %s", idx, ci->name);
309           return 1;
310         }
311     }
312   if (sec->commit) {
313     /* We have to process the whole tree of sections even if just a few changes
314      * have been made, because there are dependencies between commit-hooks and
315      * hence we need to call them in a fixed order.  */
316 #define ARY_LT_X(ary,i,x) ary[i].sec < x.sec || ary[i].sec == x.sec && ary[i].ptr < x.ptr
317     struct dirty_section comp = { sec, ptr };
318     uns pos = BIN_SEARCH_FIRST_GE_CMP(dirty.ptr, dirties, comp, ARY_LT_X);
319
320     if (commit_all
321         || (pos < dirties && dirty.ptr[pos].sec == sec && dirty.ptr[pos].ptr == ptr)) {
322       byte *msg = sec->commit(ptr);
323       if (msg) {
324         log(L_ERROR, "Cannot commit section %s: %s", name, msg);
325         return 1;
326       }
327     }
328   }
329   return 0;
330 }
331
332 static struct cf_item *
333 find_item(struct cf_section *curr_sec, byte *name, byte **msg, void **ptr)
334 {
335   *msg = NULL;
336   if (name[0] == '^')                           // absolute name instead of relative
337     name++, curr_sec = &sections, *ptr = NULL;
338   if (!curr_sec)                                // don't even search in an unknown section
339     return NULL;
340   while (1)
341   {
342     if (curr_sec != &sections)
343       add_dirty(curr_sec, *ptr);
344     byte *c = strchr(name, '.');
345     if (c)
346       *c++ = 0;
347     struct cf_item *ci = find_subitem(curr_sec, name);
348     if (!ci->cls)
349     {
350       if (!(curr_sec->flags & SEC_FLAG_UNKNOWN))        // ignore silently unknown top-level sections and unknown attributes in flagged sections
351         *msg = cf_printf("Unknown item %s", name);
352       return NULL;
353     }
354     *ptr += (addr_int_t) ci->ptr;
355     if (!c)
356       return ci;
357     if (ci->cls != CC_SECTION)
358     {
359       *msg = cf_printf("Item %s is not a section", name);
360       return NULL;
361     }
362     curr_sec = ci->u.sec;
363     name = c;
364   }
365 }
366
367 byte *
368 cf_find_item(byte *name, struct cf_item *item)
369 {
370   byte *msg;
371   void *ptr;
372   struct cf_item *ci = find_item(&sections, name, &msg, &ptr);
373   if (msg)
374     return msg;
375   if (ci) {
376     *item = *ci;
377     item->ptr = ptr;
378   } else
379     bzero(item, sizeof(struct cf_item));
380   return NULL;
381 }
382
383 /* Safe loading and reloading */
384
385 static int load_file(byte *file);
386 static int load_string(byte *string);
387
388 int
389 cf_reload(byte *file)
390 {
391   journal_swap();
392   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
393   uns ec = everything_committed;
394   everything_committed = 0;
395   int err = load_file(file);
396   if (!err)
397   {
398     for (struct old_pools *p=pools; p; p=pools)
399     {
400       pools = p->prev;
401       mp_delete(p->pool);
402     }
403     cf_journal_commit_transaction(1, NULL);
404   }
405   else
406   {
407     everything_committed = ec;
408     cf_journal_rollback_transaction(1, oldj);
409     journal_swap();
410   }
411   return err;
412 }
413
414 int
415 cf_load(byte *file)
416 {
417   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
418   int err = load_file(file);
419   if (!err)
420     cf_journal_commit_transaction(1, oldj);
421   else
422     cf_journal_rollback_transaction(1, oldj);
423   return err;
424 }
425
426 int
427 cf_set(byte *string)
428 {
429   struct cf_journal_item *oldj = cf_journal_new_transaction(0);
430   int err = load_string(string);
431   if (!err)
432     cf_journal_commit_transaction(0, oldj);
433   else
434     cf_journal_rollback_transaction(0, oldj);
435   return err;
436 }
437
438 /* Parsers for standard types */
439
440 struct unit {
441   uns name;                     // one-letter name of the unit
442   uns num, den;                 // fraction
443 };
444
445 static const struct unit units[] = {
446   { 'd', 86400, 1 },
447   { 'h', 3600, 1 },
448   { 'k', 1000, 1 },
449   { 'm', 1000000, 1 },
450   { 'g', 1000000000, 1 },
451   { 'K', 1024, 1 },
452   { 'M', 1048576, 1 },
453   { 'G', 1073741824, 1 },
454   { '%', 1, 100 },
455   { 0, 0, 0 }
456 };
457
458 static const struct unit *
459 lookup_unit(byte *value, byte *end, byte **msg)
460 {
461   if (end && *end) {
462     if (end == value || end[1] || *end >= '0' && *end <= '9')
463       *msg = "Invalid number";
464     else {
465       for (const struct unit *u=units; u->name; u++)
466         if (u->name == *end)
467           return u;
468       *msg = "Invalid unit";
469     }
470   }
471   return NULL;
472 }
473
474 static char cf_rngerr[] = "Number out of range";
475
476 byte *
477 cf_parse_int(byte *str, int *ptr)
478 {
479   byte *msg = NULL;
480   if (!*str)
481     msg = "Missing number";
482   else {
483     const struct unit *u;
484     char *end;
485     errno = 0;
486     uns x = strtoul(str, &end, 0);
487     if (errno == ERANGE)
488       msg = cf_rngerr;
489     else if (u = lookup_unit(str, end, &msg)) {
490       u64 y = (u64)x * u->num;
491       if (y % u->den)
492         msg = "Number is not an integer";
493       else {
494         y /= u->den;
495         if (y > 0xffffffff)
496           msg = cf_rngerr;
497         *ptr = y;
498       }
499     } else
500       *ptr = x;
501   }
502   return msg;
503 }
504
505 byte *
506 cf_parse_u64(byte *str, u64 *ptr)
507 {
508   byte *msg = NULL;
509   if (!*str)
510     msg = "Missing number";
511   else {
512     const struct unit *u;
513     char *end;
514     errno = 0;
515     u64 x = strtoull(str, &end, 0);
516     if (errno == ERANGE)
517       msg = cf_rngerr;
518     else if (u = lookup_unit(str, end, &msg)) {
519       if (x > ~(u64)0 / u->num)
520         msg = "Number out of range";
521       else {
522         x *= u->num;
523         if (x % u->den)
524           msg = "Number is not an integer";
525         else
526           *ptr = x / u->den;
527       }
528     } else
529       *ptr = x;
530   }
531   return msg;
532 }
533
534 byte *
535 cf_parse_double(byte *str, double *ptr)
536 {
537   byte *msg = NULL;
538   if (!*str)
539     msg = "Missing number";
540   else {
541     const struct unit *u;
542     double x;
543     uns read_chars;
544     if (sscanf(str, "%lf%n", &x, &read_chars) != 1)
545       msg = "Invalid number";
546     else if (u = lookup_unit(str, str + read_chars, &msg))
547       *ptr = x * u->num / u->den;
548     else
549       *ptr = x;
550   }
551   return msg;
552 }
553
554 byte *
555 cf_parse_ip(byte *p, u32 *varp)
556 {
557   if (!*p)
558     return "Missing IP address";
559   uns x = 0;
560   char *p2;
561   if (*p == '0' && (p[1] | 32) == 'x' && Cxdigit(p[2])) {
562     errno = 0;
563     x = strtoul(p, &p2, 16);
564     if (errno == ERANGE || x > 0xffffffff)
565       goto error;
566     p = p2;
567   }
568   else
569     for (uns i = 0; i < 4; i++) {
570       if (i) {
571         if (*p++ != '.')
572           goto error;
573       }
574       if (!Cdigit(*p))
575         goto error;
576       errno = 0;
577       uns y = strtoul(p, &p2, 10);
578       if (errno == ERANGE || p2 == (char*) p || y > 255)
579         goto error;
580       p = p2;
581       x = (x << 8) + y;
582     }
583   *varp = x;
584   return *p ? "Trailing characters" : NULL;
585 error:
586   return "Invalid IP address";
587 }
588
589 static byte *
590 cf_parse_string(byte *str, byte **ptr)
591 {
592   *ptr = cf_strdup(str);
593   return NULL;
594 }
595
596 static byte *
597 cf_parse_lookup(byte *str, int *ptr, byte **t)
598 {
599   byte **n = t;
600   uns total_len = 0;
601   while (*n && strcasecmp(*n, str)) {
602     total_len += strlen(*n) + 2;
603     n++;
604   }
605   if (*n) {
606     *ptr = n - t;
607     return NULL;
608   }
609   byte *err = cf_malloc(total_len + strlen(str) + 60), *c = err;
610   c += sprintf(err, "Invalid value %s, possible values are: ", str);
611   for (n=t; *n; n++)
612     c+= sprintf(c, "%s, ", *n);
613   if (*t)
614     c[-2] = 0;
615   *ptr = -1;
616   return err;
617 }
618
619 /* Register size of and parser for each basic type */
620
621 typedef byte *cf_basic_parser(byte *str, void *ptr);
622 static struct {
623   uns size;
624   void *parser;
625 } parsers[] = {
626   { sizeof(int), cf_parse_int },
627   { sizeof(u64), cf_parse_u64 },
628   { sizeof(double), cf_parse_double },
629   { sizeof(u32), cf_parse_ip },
630   { sizeof(byte*), cf_parse_string },
631   { sizeof(int), NULL },                        // lookups are parsed extra
632   { 0, NULL },                                  // user-defined types are parsed extra
633 };
634
635 static inline uns
636 type_size(enum cf_type type, struct cf_user_type *utype)
637 {
638   if (type < CT_USER)
639     return parsers[type].size;
640   else
641     return utype->size;
642 }
643
644 static byte *
645 cf_parse_ary(uns number, byte **pars, void *ptr, enum cf_type type, union cf_union *u)
646 {
647   for (uns i=0; i<number; i++)
648   {
649     byte *msg;
650     uns size = type_size(type, u->utype);
651     if (type < CT_LOOKUP)
652       msg = ((cf_basic_parser*) parsers[type].parser) (pars[i], ptr + i * size);
653     else if (type == CT_LOOKUP)
654       msg = cf_parse_lookup(pars[i], ptr + i * size, u->lookup);
655     else if (type == CT_USER)
656       msg = u->utype->parser(pars[i], ptr + i * size);
657     else
658       ASSERT(0);
659     if (msg)
660       return cf_printf("Cannot parse item %d: %s", i+1, msg);
661   }
662   return NULL;
663 }
664
665 /* Interpreter */
666
667 #define T(x) #x,
668 static byte *op_names[] = { CF_OPERATIONS };
669 #undef T
670
671 #define OP_MASK 0xff            // only get the operation
672 #define OP_OPEN 0x100           // here we only get an opening brace instead of parameters
673 #define OP_1ST 0x200            // in the 1st phase selectors are recorded into the mask
674 #define OP_2ND 0x400            // in the 2nd phase real data are entered
675
676 static byte *
677 interpret_set_dynamic(struct cf_item *item, int number, byte **pars, void **ptr)
678 {
679   enum cf_type type = item->type;
680   cf_journal_block(ptr, sizeof(void*));
681   // boundary checks done by the caller
682   uns size = type_size(item->type, item->u.utype);
683   ASSERT(size >= sizeof(uns));
684   *ptr = cf_malloc((number+1) * size) + size;
685   * (uns*) (*ptr - size) = number;
686   return cf_parse_ary(number, pars, *ptr, type, &item->u);
687 }
688
689 static byte *
690 interpret_add_dynamic(struct cf_item *item, int number, byte **pars, int *processed, void **ptr, enum cf_operation op)
691 {
692   enum cf_type type = item->type;
693   void *old_p = *ptr;
694   uns size = type_size(item->type, item->u.utype);
695   ASSERT(size >= sizeof(uns));
696   int old_nr = * (int*) (old_p - size);
697   int taken = MIN(number, ABS(item->number)-old_nr);
698   *processed = taken;
699   // stretch the dynamic array
700   void *new_p = cf_malloc((old_nr + taken + 1) * size) + size;
701   * (uns*) (new_p - size) = old_nr + taken;
702   cf_journal_block(ptr, sizeof(void*));
703   *ptr = new_p;
704   if (op == OP_APPEND) {
705     memcpy(new_p, old_p, old_nr * size);
706     return cf_parse_ary(taken, pars, new_p + old_nr * size, type, &item->u);
707   } else if (op == OP_PREPEND) {
708     memcpy(new_p + taken * size, old_p, old_nr * size);
709     return cf_parse_ary(taken, pars, new_p, type, &item->u);
710   } else
711     return cf_printf("Dynamic arrays do not support operation %s", op_names[op]);
712 }
713
714 static byte *interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic);
715
716 static byte *
717 interpret_section(struct cf_section *sec, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
718 {
719   add_dirty(sec, ptr);
720   *processed = 0;
721   for (struct cf_item *ci=sec->cfg; ci->cls; ci++)
722   {
723     int taken;
724     byte *msg = interpret_set_item(ci, number, pars, &taken, ptr + (addr_int_t) ci->ptr, allow_dynamic && !ci[1].cls);
725     if (msg)
726       return cf_printf("Item %s: %s", ci->name, msg);
727     *processed += taken;
728     number -= taken;
729     pars += taken;
730     if (!number)                // stop parsing, because many parsers would otherwise complain that number==0
731       break;
732   }
733   return NULL;
734 }
735
736 static void
737 add_to_list(struct cnode *where, struct cnode *new_node, enum cf_operation op)
738 {
739   switch (op)
740   {
741     case OP_EDIT:               // edition has been done in-place
742       break;
743     case OP_REMOVE:
744       cf_journal_block(&where->prev->next, sizeof(void*));
745       cf_journal_block(&where->next->prev, sizeof(void*));
746       clist_remove(where);
747       break;
748     case OP_AFTER:              // implementation dependend (prepend_head = after(list)), and where==list, see clists.h:74
749     case OP_PREPEND:
750       cf_journal_block(&where->next->prev, sizeof(void*));
751       cf_journal_block(&where->next, sizeof(void*));
752       clist_insert_after(new_node, where);
753       break;
754     case OP_BEFORE:             // implementation dependend (append_tail = before(list))
755     case OP_APPEND:
756     case OP_SET:
757       cf_journal_block(&where->prev->next, sizeof(void*));
758       cf_journal_block(&where->prev, sizeof(void*));
759       clist_insert_before(new_node, where);
760       break;
761     default:
762       ASSERT(0);
763   }
764 }
765
766 static byte *
767 interpret_add_list(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, enum cf_operation op)
768 {
769   if (op >= OP_REMOVE)
770     return cf_printf("You have to open a block for operation %s", op_names[op]);
771   if (!number)
772     return "Nothing to add to the list";
773   struct cf_section *sec = item->u.sec;
774   *processed = 0;
775   while (number > 0)
776   {
777     void *node = cf_malloc(sec->size);
778     cf_init_section(item->name, sec, node, 1);
779     add_to_list(ptr, node, op);
780     int taken;
781     /* If the node contains any dynamic attribute at the end, we suppress
782      * auto-repetition here and pass the flag inside instead.  */
783     TRY( interpret_section(sec, number, pars, &taken, node, sec->flags & SEC_FLAG_DYNAMIC) );
784     *processed += taken;
785     number -= taken;
786     pars += taken;
787     if (sec->flags & SEC_FLAG_DYNAMIC)
788       break;
789   }
790   return NULL;
791 }
792
793 static byte *
794 interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
795 {
796   int taken;
797   switch (item->cls)
798   {
799     case CC_STATIC:
800       if (!number)
801         return "Missing value";
802       taken = MIN(number, item->number);
803       *processed = taken;
804       uns size = type_size(item->type, item->u.utype);
805       cf_journal_block(ptr, taken * size);
806       return cf_parse_ary(taken, pars, ptr, item->type, &item->u);
807     case CC_DYNAMIC:
808       if (!allow_dynamic)
809         return "Dynamic array cannot be used here";
810       taken = MIN(number, ABS(item->number));
811       *processed = taken;
812       return interpret_set_dynamic(item, taken, pars, ptr);
813     case CC_PARSER:
814       if (item->number < 0 && !allow_dynamic)
815         return "Parsers with variable number of parameters cannot be used here";
816       if (item->number > 0 && number < item->number)
817         return "Not enough parameters available for the parser";
818       taken = MIN(number, ABS(item->number));
819       *processed = taken;
820       for (int i=0; i<taken; i++)
821         pars[i] = cf_strdup(pars[i]);
822       return item->u.par(taken, pars, ptr);
823     case CC_SECTION:
824       return interpret_section(item->u.sec, number, pars, processed, ptr, allow_dynamic);
825     case CC_LIST:
826       if (!allow_dynamic)
827         return "Lists cannot be used here";
828       return interpret_add_list(item, number, pars, processed, ptr, OP_SET);
829     default:
830       ASSERT(0);
831   }
832 }
833
834 static byte *
835 interpret_clear(struct cf_item *item, void *ptr)
836 {
837   if (item->cls == CC_LIST) {
838     cf_journal_block(ptr, sizeof(struct clist));
839     clist_init(ptr);
840   } else if (item->cls == CC_DYNAMIC) {
841     cf_journal_block(ptr, sizeof(void *));
842     * (void**) ptr = NULL;
843   } else
844     return "The item is not a list or a dynamic array";
845   return NULL;
846 }
847
848 static int
849 cmp_items(void *i1, void *i2, struct cf_item *item)
850 {
851   ASSERT(item->cls == CC_STATIC);
852   i1 += (addr_int_t) item->ptr;
853   i2 += (addr_int_t) item->ptr;
854   if (item->type == CT_STRING)
855     return strcmp(* (byte**) i1, * (byte**) i2);
856   else                          // all numeric types
857     return memcmp(i1, i2, type_size(item->type, item->u.utype));
858 }
859
860 static void *
861 find_list_node(struct clist *list, void *query, struct cf_section *sec, u32 mask)
862 {
863   struct cnode *n;
864   CLIST_WALK(n, *list)
865   {
866     uns found = 1;
867     for (uns i=0; i<32; i++)
868       if (mask & (1<<i))
869         if (cmp_items(n, query, sec->cfg+i))
870         {
871           found = 0;
872           break;
873         }
874     if (found)
875       return n;
876   }
877   return NULL;
878 }
879
880 static byte *
881 record_selector(struct cf_item *item, struct cf_section *sec, u32 *mask)
882 {
883   uns nr = sec->flags & SEC_FLAG_NUMBER;
884   if (item >= sec->cfg && item < sec->cfg + nr) // setting an attribute relative to this section
885   {
886     uns i = item - sec->cfg;
887     if (i >= 32)
888       return "Cannot select list nodes by this attribute";
889     if (sec->cfg[i].cls != CC_STATIC)
890       return "Selection can only be done based on basic attributes";
891     *mask |= 1 << i;
892   }
893   return NULL;
894 }
895
896 #define MAX_STACK_SIZE  100
897 static struct item_stack {
898   struct cf_section *sec;       // nested section
899   void *base_ptr;               // because original pointers are often relative
900   enum cf_operation op;         // it is performed when a closing brace is encountered
901   void *list;                   // list the operations should be done on
902   u32 mask;                     // bit array of selectors searching in a list
903   struct cf_item *item;         // cf_item of the list
904 } stack[MAX_STACK_SIZE];
905 static uns level;
906
907 static byte *
908 opening_brace(struct cf_item *item, void *ptr, enum cf_operation op)
909 {
910   if (level >= MAX_STACK_SIZE-1)
911     return "Too many nested sections";
912   stack[++level] = (struct item_stack) {
913     .sec = NULL,
914     .base_ptr = NULL,
915     .op = op & OP_MASK,
916     .list = NULL,
917     .mask = 0,
918     .item = NULL,
919   };
920   if (!item)                    // unknown is ignored; we just need to trace recursion
921     return NULL;
922   stack[level].sec = item->u.sec;
923   if (item->cls == CC_SECTION)
924   {
925     stack[level].base_ptr = ptr;
926     stack[level].op = OP_EDIT | OP_2ND; // this list operation does nothing
927   }
928   else if (item->cls == CC_LIST)
929   {
930     stack[level].base_ptr = cf_malloc(item->u.sec->size);
931     cf_init_section(item->name, item->u.sec, stack[level].base_ptr, 1);
932     stack[level].list = ptr;
933     stack[level].item = item;
934     if ((op & OP_MASK) < OP_REMOVE) {
935       add_to_list(ptr, stack[level].base_ptr, op & OP_MASK);
936       stack[level].op |= OP_2ND;
937     } else
938       stack[level].op |= OP_1ST;
939   }
940   else
941     return "Opening brace can only be used on sections and lists";
942   return NULL;
943 }
944
945 static byte *
946 closing_brace(struct item_stack *st, enum cf_operation op, int number, byte **pars)
947 {
948   if (st->op == OP_CLOSE)       // top-level
949     return "Unmatched } parenthesis";
950   if (!st->sec) {               // dummy run on unknown section
951     if (!(op & OP_OPEN))
952       level--;
953     return NULL;
954   }
955   enum cf_operation pure_op = st->op & OP_MASK;
956   if (st->op & OP_1ST)
957   {
958     st->list = find_list_node(st->list, st->base_ptr, st->sec, st->mask);
959     if (!st->list)
960       return "Cannot find a node matching the query";
961     if (pure_op != OP_REMOVE)
962     {
963       if (pure_op == OP_EDIT)
964         st->base_ptr = st->list;
965       else if (pure_op == OP_AFTER || pure_op == OP_BEFORE)
966         cf_init_section(st->item->name, st->sec, st->base_ptr, 1);
967       else
968         ASSERT(0);
969       if (op & OP_OPEN) {       // stay at the same recursion level
970         st->op = (st->op | OP_2ND) & ~OP_1ST;
971         add_to_list(st->list, st->base_ptr, pure_op);
972         return NULL;
973       }
974       int taken;                // parse parameters on 1 line immediately
975       TRY( interpret_section(st->sec, number, pars, &taken, st->base_ptr, 1) );
976       number -= taken;
977       pars += taken;
978       // and fall-thru to the 2nd phase
979     }
980     add_to_list(st->list, st->base_ptr, pure_op);
981   }
982   level--;
983   if (number)
984     return "No parameters expected after the }";
985   else if (op & OP_OPEN)
986     return "No { is expected";
987   else
988     return NULL;
989 }
990
991 static byte *
992 interpret_line(byte *name, enum cf_operation op, int number, byte **pars)
993 {
994   byte *msg;
995   if ((op & OP_MASK) == OP_CLOSE)
996     return closing_brace(stack+level, op, number, pars);
997   void *ptr = stack[level].base_ptr;
998   struct cf_item *item = find_item(stack[level].sec, name, &msg, &ptr);
999   if (msg)
1000     return msg;
1001   if (stack[level].op & OP_1ST)
1002     TRY( record_selector(item, stack[level].sec, &stack[level].mask) );
1003   if (op & OP_OPEN) {           // the operation will be performed after the closing brace
1004     if (number)
1005       return "Cannot open a block after a parameter has been passed on a line";
1006     return opening_brace(item, ptr, op);
1007   }
1008   if (!item)                    // ignored item in an unknown section
1009     return NULL;
1010   op &= OP_MASK;
1011
1012   int taken;                    // process as many parameters as possible
1013   if (op == OP_CLEAR)
1014     taken = 0, msg = interpret_clear(item, ptr);
1015   else if (op == OP_SET)
1016     msg = interpret_set_item(item, number, pars, &taken, ptr, 1);
1017   else if (item->cls == CC_DYNAMIC)
1018     msg = interpret_add_dynamic(item, number, pars, &taken, ptr, op);
1019   else if (item->cls == CC_LIST)
1020     msg = interpret_add_list(item, number, pars, &taken, ptr, op);
1021   else
1022     return cf_printf("Operation %s not supported on attribute %s", op_names[op], name);
1023   if (msg)
1024     return msg;
1025   if (taken < number)
1026     return cf_printf("Too many parameters: %d>%d", number, taken);
1027
1028   return NULL;
1029 }
1030
1031 byte *
1032 cf_write_item(struct cf_item *item, enum cf_operation op, int number, byte **pars)
1033 {
1034   byte *msg;
1035   int taken;
1036   switch (op) {
1037     case OP_SET:
1038       msg = interpret_set_item(item, number, pars, &taken, item->ptr, 1);
1039       break;
1040     case OP_CLEAR:
1041       taken = 0;
1042       msg = interpret_clear(item, item->ptr);
1043       break;
1044     case OP_APPEND:
1045     case OP_PREPEND:
1046       if (item->cls == CC_DYNAMIC)
1047         msg = interpret_add_dynamic(item, number, pars, &taken, item->ptr, op);
1048       else if (item->cls == CC_LIST)
1049         msg = interpret_add_list(item, number, pars, &taken, item->ptr, op);
1050       else
1051         return "The attribute class does not support append/prepend";
1052       break;
1053     default:
1054       return "Unsupported operation";
1055   }
1056   if (msg)
1057     return msg;
1058   if (taken < number)
1059     return "Too many parameters";
1060   return NULL;
1061 }
1062
1063 static void
1064 init_stack(void)
1065 {
1066   global_init();
1067   level = 0;
1068   stack[0] = (struct item_stack) {
1069     .sec = &sections,
1070     .base_ptr = NULL,
1071     .op = OP_CLOSE,
1072     .list = NULL,
1073     .mask = 0,
1074     .item = NULL
1075   };
1076 }
1077
1078 static int
1079 done_stack(void)
1080 {
1081   if (level > 0) {
1082     log(L_ERROR, "Unterminated block");
1083     return 1;
1084   }
1085   sort_dirty();
1086   if (commit_section("top-level", &sections, NULL, !everything_committed))
1087     return 1;
1088   everything_committed = 1;
1089   dirties = 0;
1090   return 0;
1091 }
1092
1093 /* Text file parser */
1094
1095 static byte *name_parse_fb;
1096 static struct fastbuf *parse_fb;
1097 static uns line_num;
1098
1099 #define MAX_LINE        4096
1100 static byte line_buf[MAX_LINE];
1101 static byte *line = line_buf;
1102
1103 #include "lib/bbuf.h"
1104 static bb_t copy_buf;
1105 static uns copied;
1106
1107 #define GBUF_TYPE       uns
1108 #define GBUF_PREFIX(x)  split_##x
1109 #include "lib/gbuf.h"
1110 static split_t word_buf;
1111 static uns words;
1112 static uns ends_by_brace;               // the line is ended by "{"
1113
1114 static int
1115 get_line(void)
1116 {
1117   if (!bgets(parse_fb, line_buf, MAX_LINE))
1118     return 0;
1119   line_num++;
1120   line = line_buf;
1121   while (Cblank(*line))
1122     line++;
1123   return 1;
1124 }
1125
1126 static void
1127 append(byte *start, byte *end)
1128 {
1129   uns len = end - start;
1130   bb_grow(&copy_buf, copied + len + 1);
1131   memcpy(copy_buf.ptr + copied, start, len);
1132   copied += len + 1;
1133   copy_buf.ptr[copied-1] = 0;
1134 }
1135
1136 #define CONTROL_CHAR(x) (x == '{' || x == '}' || x == ';')
1137   // these characters separate words like blanks
1138
1139 static byte *
1140 get_word(uns is_command_name)
1141 {
1142   if (*line == '\'') {
1143     line++;
1144     while (1) {
1145       byte *start = line;
1146       while (*line && *line != '\'')
1147         line++;
1148       append(start, line);
1149       if (*line)
1150         break;
1151       copy_buf.ptr[copied-1] = '\n';
1152       if (!get_line())
1153         return "Unterminated apostrophe word at the end";
1154     }
1155     line++;
1156
1157   } else if (*line == '"') {
1158     line++;
1159     uns start_copy = copied;
1160     while (1) {
1161       byte *start = line;
1162       uns escape = 0;
1163       while (*line) {
1164         if (*line == '"' && !escape)
1165           break;
1166         else if (*line == '\\')
1167           escape ^= 1;
1168         else
1169           escape = 0;
1170         line++;
1171       }
1172       append(start, line);
1173       if (*line)
1174         break;
1175       if (!escape)
1176         copy_buf.ptr[copied-1] = '\n';
1177       else // merge two lines
1178         copied -= 2;
1179       if (!get_line())
1180         return "Unterminated quoted word at the end";
1181     }
1182     line++;
1183
1184     byte *tmp = stk_str_unesc(copy_buf.ptr + start_copy);
1185     uns l = strlen(tmp);
1186     bb_grow(&copy_buf, start_copy + l + 1);
1187     strcpy(copy_buf.ptr + start_copy, tmp);
1188     copied = start_copy + l + 1;
1189
1190   } else {
1191     // promised that *line is non-null and non-blank
1192     byte *start = line;
1193     while (*line && !Cblank(*line) && !CONTROL_CHAR(*line)
1194         && (*line != '=' || !is_command_name))
1195       line++;
1196     if (*line == '=') {                         // nice for setting from a command-line
1197       if (line == start)
1198         return "Assignment without a variable";
1199       *line = ' ';
1200     }
1201     if (line == start)                          // already the first char is control
1202       line++;
1203     append(start, line);
1204   }
1205   while (Cblank(*line))
1206     line++;
1207   return NULL;
1208 }
1209
1210 static byte *
1211 get_token(uns is_command_name, byte **msg)
1212 {
1213   *msg = NULL;
1214   while (1) {
1215     if (!*line || *line == '#') {
1216       if (!is_command_name || !get_line())
1217         return NULL;
1218     } else if (*line == ';') {
1219       *msg = get_word(0);
1220       if (!is_command_name || *msg)
1221         return NULL;
1222     } else if (*line == '\\' && !line[1]) {
1223       if (!get_line()) {
1224         *msg = "Last line ends by a backslash";
1225         return NULL;
1226       }
1227       if (!*line || *line == '#')
1228         log(L_WARN, "The line %s:%d following a backslash is empty", name_parse_fb, line_num);
1229     } else {
1230       split_grow(&word_buf, words+1);
1231       uns start = copied;
1232       word_buf.ptr[words++] = copied;
1233       *msg = get_word(is_command_name);
1234       return *msg ? NULL : copy_buf.ptr + start;
1235     }
1236   }
1237 }
1238
1239 static byte *
1240 split_command(void)
1241 {
1242   words = copied = ends_by_brace = 0;
1243   byte *msg, *start_word;
1244   if (!(start_word = get_token(1, &msg)))
1245     return msg;
1246   if (*start_word == '{')                       // only one opening brace
1247     return "Unexpected opening brace";
1248   while (*line != '}')                          // stays for the next time
1249   {
1250     if (!(start_word = get_token(0, &msg)))
1251       return msg;
1252     if (*start_word == '{') {
1253       words--;                                  // discard the brace
1254       ends_by_brace = 1;
1255       break;
1256     }
1257   }
1258   return NULL;
1259 }
1260
1261 /* Parsing multiple files */
1262
1263 static byte *
1264 parse_fastbuf(byte *name_fb, struct fastbuf *fb, uns depth)
1265 {
1266   byte *msg;
1267   name_parse_fb = name_fb;
1268   parse_fb = fb;
1269   line_num = 0;
1270   line = line_buf;
1271   *line = 0;
1272   while (1)
1273   {
1274     msg = split_command();
1275     if (msg)
1276       goto error;
1277     if (!words)
1278       return NULL;
1279     byte *name = copy_buf.ptr + word_buf.ptr[0];
1280     byte *pars[words-1];
1281     for (uns i=1; i<words; i++)
1282       pars[i-1] = copy_buf.ptr + word_buf.ptr[i];
1283     if (!strcasecmp(name, "include"))
1284     {
1285       if (words != 2)
1286         msg = "Expecting one filename";
1287       else if (depth > 8)
1288         msg = "Too many nested files";
1289       else if (*line && *line != '#')           // because the contents of line_buf is not re-entrant and will be cleared
1290         msg = "The input command must be the last one on a line";
1291       if (msg)
1292         goto error;
1293       struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
1294       if (!new_fb) {
1295         msg = cf_printf("Cannot open file %s: %m", pars[0]);
1296         goto error;
1297       }
1298       uns ll = line_num;
1299       msg = parse_fastbuf(pars[0], new_fb, depth+1);
1300       bclose(new_fb);
1301       if (msg)
1302         goto error;
1303       line_num = ll;
1304       parse_fb = fb;
1305     }
1306     enum cf_operation op;
1307     byte *c = strchr(name, ':');
1308     if (!c)
1309       op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
1310     else {
1311       *c++ = 0;
1312       switch (Clocase(*c)) {
1313         case 's': op = OP_SET; break;
1314         case 'c': op = OP_CLEAR; break;
1315         case 'a': op = Clocase(c[1]) == 'p' ? OP_APPEND : OP_AFTER; break;
1316         case 'p': op = OP_PREPEND; break;
1317         case 'r': op = OP_REMOVE; break;
1318         case 'e': op = OP_EDIT; break;
1319         case 'b': op = OP_BEFORE; break;
1320         default: op = OP_SET; break;
1321       }
1322       if (strcasecmp(c, op_names[op])) {
1323         msg = cf_printf("Unknown operation %s", c);
1324         goto error;
1325       }
1326     }
1327     if (ends_by_brace)
1328       op |= OP_OPEN;
1329     msg = interpret_line(name, op, words-1, pars);
1330     if (msg)
1331       goto error;
1332   }
1333 error:
1334   log(L_ERROR, "File %s, line %d: %s", name_fb, line_num, msg);
1335   return "included from here";
1336 }
1337
1338 #undef DEFAULT_CONFIG                   /* FIXME */
1339 #define DEFAULT_CONFIG "cf/sherlock2"
1340
1341 #ifndef DEFAULT_CONFIG
1342 #define DEFAULT_CONFIG NULL
1343 #endif
1344 byte *cf_def_file = DEFAULT_CONFIG;
1345
1346 static int
1347 load_file(byte *file)
1348 {
1349   init_stack();
1350   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
1351   if (!fb) {
1352     log(L_ERROR, "Cannot open %s: %m", file);
1353     return 1;
1354   }
1355   byte *msg = parse_fastbuf(file, fb, 0);
1356   bclose(fb);
1357   int err = !!msg || done_stack();
1358   if (!err)
1359     cf_def_file = NULL;
1360   return err;
1361 }
1362
1363 static int
1364 load_string(byte *string)
1365 {
1366   init_stack();
1367   struct fastbuf fb;
1368   fbbuf_init_read(&fb, string, strlen(string), 0);
1369   byte *msg = parse_fastbuf("memory string", &fb, 0);
1370   return !!msg || done_stack();
1371 }
1372
1373 /* Command-line parser */
1374
1375 static void
1376 load_default(void)
1377 {
1378   if (cf_def_file)
1379     if (cf_load(cf_def_file))
1380       die("Cannot load default config %s", cf_def_file);
1381 }
1382
1383 int
1384 cf_get_opt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
1385 {
1386   static int other_options = 0;
1387   while (1) {
1388     int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
1389     if (res == 'S' || res == 'C' || res == 0x64436667)
1390     {
1391       if (other_options)
1392         die("The -S and -C options must precede all other arguments");
1393       if (res == 'S') {
1394         load_default();
1395         if (cf_set(optarg))
1396           die("Cannot set %s", optarg);
1397       } else if (res == 'C') {
1398         if (cf_load(optarg))
1399           die("Cannot load config file %s", optarg);
1400       }
1401 #ifdef CONFIG_DEBUG
1402       else {   /* --dumpconfig */
1403         load_default();
1404         struct fastbuf *b = bfdopen(1, 4096);
1405         cf_dump_sections(b);
1406         bclose(b);
1407       }
1408 #endif
1409     } else {
1410       /* unhandled option or end of options */
1411       if (res != ':' && res != '?')
1412         load_default();
1413       other_options++;
1414       return res;
1415     }
1416   }
1417 }
1418
1419 /* Debug dumping */
1420
1421 static void
1422 spaces(struct fastbuf *fb, uns nr)
1423 {
1424   for (uns i=0; i<nr; i++)
1425     bputs(fb, "  ");
1426 }
1427
1428 static void
1429 dump_basic(struct fastbuf *fb, void *ptr, enum cf_type type, union cf_union *u)
1430 {
1431   switch (type) {
1432     case CT_INT:        bprintf(fb, "%d ", *(uns*)ptr); break;
1433     case CT_U64:        bprintf(fb, "%llu ", *(u64*)ptr); break;
1434     case CT_DOUBLE:     bprintf(fb, "%lg ", *(double*)ptr); break;
1435     case CT_IP:         bprintf(fb, "%08x ", *(uns*)ptr); break;
1436     case CT_STRING:
1437       if (*(byte**)ptr)
1438         bprintf(fb, "'%s' ", *(byte**)ptr);
1439       else
1440         bprintf(fb, "NULL ");
1441       break;
1442     case CT_LOOKUP:     bprintf(fb, "%s ", *(int*)ptr >= 0 ? u->lookup[ *(int*)ptr ] : (byte*) "???"); break;
1443     case CT_USER:
1444       if (u->utype->dumper)
1445         u->utype->dumper(fb, ptr);
1446       else
1447         bprintf(fb, "??? ");
1448       break;
1449   }
1450 }
1451
1452 static void dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr);
1453
1454 static byte *class_names[] = { "end", "static", "dynamic", "parser", "section", "list" };
1455 static byte *type_names[] = { "int", "u64", "double", "ip", "string", "lookup", "user" };
1456
1457 static void
1458 dump_item(struct fastbuf *fb, struct cf_item *item, int level, void *ptr)
1459 {
1460   ptr += (addr_int_t) item->ptr;
1461   enum cf_type type = item->type;
1462   uns size = type_size(item->type, item->u.utype);
1463   int i;
1464   spaces(fb, level);
1465   bprintf(fb, "%s: C%s #", item->name, class_names[item->cls]);
1466   if (item->number == CF_ANY_NUM)
1467     bputs(fb, "any ");
1468   else
1469     bprintf(fb, "%d ", item->number);
1470   if (item->cls == CC_STATIC || item->cls == CC_DYNAMIC) {
1471     bprintf(fb, "T%s ", type_names[type]);
1472     if (item->type == CT_USER)
1473       bprintf(fb, "U%s S%d ", item->u.utype->name, size);
1474   }
1475   if (item->cls == CC_STATIC) {
1476     for (i=0; i<item->number; i++)
1477       dump_basic(fb, ptr + i * size, type, &item->u);
1478   } else if (item->cls == CC_DYNAMIC) {
1479     ptr = * (void**) ptr;
1480     if (ptr) {
1481       int real_nr = * (int*) (ptr - size);
1482       bprintf(fb, "N%d ", real_nr);
1483       for (i=0; i<real_nr; i++)
1484         dump_basic(fb, ptr + i * size, type, &item->u);
1485     } else
1486       bprintf(fb, "NULL ");
1487   }
1488   bputc(fb, '\n');
1489   if (item->cls == CC_SECTION)
1490     dump_section(fb, item->u.sec, level+1, ptr);
1491   else if (item->cls == CC_LIST) {
1492     uns idx = 0;
1493     struct cnode *n;
1494     CLIST_WALK(n, * (clist*) ptr) {
1495       spaces(fb, level+1);
1496       bprintf(fb, "item %d\n", ++idx);
1497       dump_section(fb, item->u.sec, level+2, n);
1498     }
1499   }
1500 }
1501
1502 static void
1503 dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr)
1504 {
1505   spaces(fb, level);
1506   bprintf(fb, "S%d F%x:\n", sec->size, sec->flags);
1507   for (struct cf_item *item=sec->cfg; item->cls; item++)
1508     dump_item(fb, item, level, ptr);
1509 }
1510
1511 void
1512 cf_dump_sections(struct fastbuf *fb)
1513 {
1514   dump_section(fb, &sections, 0, NULL);
1515 }
1516
1517 /* TODO
1518  * - more space efficient journal
1519  */