]> mj.ucw.cz Git - libucw.git/commitdiff
conf2.c split into 7 smaller modules conf-*.c with include conf-internal.h
authorRobert Spalek <robert@ucw.cz>
Fri, 28 Apr 2006 14:31:02 +0000 (16:31 +0200)
committerRobert Spalek <robert@ucw.cz>
Fri, 28 Apr 2006 14:31:02 +0000 (16:31 +0200)
13 files changed:
lib/Makefile
lib/conf-alloc.c [new file with mode: 0644]
lib/conf-dump.c [new file with mode: 0644]
lib/conf-input.c [new file with mode: 0644]
lib/conf-internal.h [new file with mode: 0644]
lib/conf-intr.c [new file with mode: 0644]
lib/conf-journal.c [new file with mode: 0644]
lib/conf-parse.c [new file with mode: 0644]
lib/conf-section.c [new file with mode: 0644]
lib/conf-test.c
lib/conf-test.t [new file with mode: 0644]
lib/conf2.c [deleted file]
lib/conf2.t [deleted file]

index f163c34dbd5edeadee2053fd6f321e85e13272b9..0991fc9906f7c35a2c616b8c9e49f40057481dfb 100644 (file)
@@ -11,7 +11,8 @@ LIBUCW_MODS= \
        mmap pagecache partmap hashfunc \
        lists slists sorter bitsig \
        log log-file proctitle \
-       conf2 ipaccess \
+       conf-alloc conf-dump conf-input conf-intr conf-journal conf-parse conf-section \
+       ipaccess \
        profile \
        fastbuf ff-printf ff-utf8 \
        fb-file carefulio fb-mem fb-temp fb-mmap fb-limfd fb-buffer fb-grow \
diff --git a/lib/conf-alloc.c b/lib/conf-alloc.c
new file mode 100644 (file)
index 0000000..8553e7f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *     UCW Library -- Configuration files: memory allocation
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/mempool.h"
+
+struct mempool *cf_pool;       // current pool for loading new configuration
+
+void *
+cf_malloc(uns size)
+{
+  return mp_alloc(cf_pool, size);
+}
+
+void *
+cf_malloc_zero(uns size)
+{
+  return mp_alloc_zero(cf_pool, size);
+}
+
+byte *
+cf_strdup(byte *s)
+{
+  return mp_strdup(cf_pool, s);
+}
+
+byte *
+cf_printf(char *fmt, ...)
+{
+  va_list args;
+  va_start(args, fmt);
+  byte *res = mp_vprintf(cf_pool, fmt, args);
+  va_end(args);
+  return res;
+}
diff --git a/lib/conf-dump.c b/lib/conf-dump.c
new file mode 100644 (file)
index 0000000..fe4a7c2
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ *     UCW Library -- Configuration files: dumping
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/getopt.h"
+#include "lib/conf-internal.h"
+#include "lib/clists.h"
+#include "lib/fastbuf.h"
+
+static void
+spaces(struct fastbuf *fb, uns nr)
+{
+  for (uns i=0; i<nr; i++)
+    bputs(fb, "  ");
+}
+
+static void
+dump_basic(struct fastbuf *fb, void *ptr, enum cf_type type, union cf_union *u)
+{
+  switch (type) {
+    case CT_INT:       bprintf(fb, "%d ", *(uns*)ptr); break;
+    case CT_U64:       bprintf(fb, "%llu ", *(u64*)ptr); break;
+    case CT_DOUBLE:    bprintf(fb, "%lg ", *(double*)ptr); break;
+    case CT_IP:                bprintf(fb, "%08x ", *(uns*)ptr); break;
+    case CT_STRING:
+      if (*(byte**)ptr)
+       bprintf(fb, "'%s' ", *(byte**)ptr);
+      else
+       bprintf(fb, "NULL ");
+      break;
+    case CT_LOOKUP:    bprintf(fb, "%s ", *(int*)ptr >= 0 ? u->lookup[ *(int*)ptr ] : (byte*) "???"); break;
+    case CT_USER:
+      if (u->utype->dumper)
+       u->utype->dumper(fb, ptr);
+      else
+       bprintf(fb, "??? ");
+      break;
+  }
+}
+
+static void dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr);
+
+static byte *class_names[] = { "end", "static", "dynamic", "parser", "section", "list" };
+static byte *type_names[] = { "int", "u64", "double", "ip", "string", "lookup", "user" };
+
+static void
+dump_item(struct fastbuf *fb, struct cf_item *item, int level, void *ptr)
+{
+  ptr += (addr_int_t) item->ptr;
+  enum cf_type type = item->type;
+  uns size = cf_type_size(item->type, item->u.utype);
+  int i;
+  spaces(fb, level);
+  bprintf(fb, "%s: C%s #", item->name, class_names[item->cls]);
+  if (item->number == CF_ANY_NUM)
+    bputs(fb, "any ");
+  else
+    bprintf(fb, "%d ", item->number);
+  if (item->cls == CC_STATIC || item->cls == CC_DYNAMIC) {
+    bprintf(fb, "T%s ", type_names[type]);
+    if (item->type == CT_USER)
+      bprintf(fb, "U%s S%d ", item->u.utype->name, size);
+  }
+  if (item->cls == CC_STATIC) {
+    for (i=0; i<item->number; i++)
+      dump_basic(fb, ptr + i * size, type, &item->u);
+  } else if (item->cls == CC_DYNAMIC) {
+    ptr = * (void**) ptr;
+    if (ptr) {
+      int real_nr = * (int*) (ptr - size);
+      bprintf(fb, "N%d ", real_nr);
+      for (i=0; i<real_nr; i++)
+       dump_basic(fb, ptr + i * size, type, &item->u);
+    } else
+      bprintf(fb, "NULL ");
+  }
+  bputc(fb, '\n');
+  if (item->cls == CC_SECTION)
+    dump_section(fb, item->u.sec, level+1, ptr);
+  else if (item->cls == CC_LIST) {
+    uns idx = 0;
+    CLIST_FOR_EACH(cnode *, n, * (clist*) ptr) {
+      spaces(fb, level+1);
+      bprintf(fb, "item %d\n", ++idx);
+      dump_section(fb, item->u.sec, level+2, n);
+    }
+  }
+}
+
+static void
+dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr)
+{
+  spaces(fb, level);
+  bprintf(fb, "S%d F%x:\n", sec->size, sec->flags);
+  for (struct cf_item *item=sec->cfg; item->cls; item++)
+    dump_item(fb, item, level, ptr);
+}
+
+void
+cf_dump_sections(struct fastbuf *fb)
+{
+  dump_section(fb, &cf_sections, 0, NULL);
+}
+
diff --git a/lib/conf-input.c b/lib/conf-input.c
new file mode 100644 (file)
index 0000000..786725b
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+ *     UCW Library -- Configuration files: parsing input streams
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/getopt.h"
+#include "lib/conf-internal.h"
+#include "lib/mempool.h"
+#include "lib/fastbuf.h"
+#include "lib/chartype.h"
+#include "lib/stkstring.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+/* Text file parser */
+
+static byte *name_parse_fb;
+static struct fastbuf *parse_fb;
+static uns line_num;
+
+#define MAX_LINE       4096
+static byte line_buf[MAX_LINE];
+static byte *line = line_buf;
+
+#include "lib/bbuf.h"
+static bb_t copy_buf;
+static uns copied;
+
+#define GBUF_TYPE      uns
+#define GBUF_PREFIX(x) split_##x
+#include "lib/gbuf.h"
+static split_t word_buf;
+static uns words;
+static uns ends_by_brace;              // the line is ended by "{"
+
+static int
+get_line(void)
+{
+  if (!bgets(parse_fb, line_buf, MAX_LINE))
+    return 0;
+  line_num++;
+  line = line_buf;
+  while (Cblank(*line))
+    line++;
+  return 1;
+}
+
+static void
+append(byte *start, byte *end)
+{
+  uns len = end - start;
+  bb_grow(&copy_buf, copied + len + 1);
+  memcpy(copy_buf.ptr + copied, start, len);
+  copied += len + 1;
+  copy_buf.ptr[copied-1] = 0;
+}
+
+#define        CONTROL_CHAR(x) (x == '{' || x == '}' || x == ';')
+  // these characters separate words like blanks
+
+static byte *
+get_word(uns is_command_name)
+{
+  if (*line == '\'') {
+    line++;
+    while (1) {
+      byte *start = line;
+      while (*line && *line != '\'')
+       line++;
+      append(start, line);
+      if (*line)
+       break;
+      copy_buf.ptr[copied-1] = '\n';
+      if (!get_line())
+       return "Unterminated apostrophe word at the end";
+    }
+    line++;
+
+  } else if (*line == '"') {
+    line++;
+    uns start_copy = copied;
+    while (1) {
+      byte *start = line;
+      uns escape = 0;
+      while (*line) {
+       if (*line == '"' && !escape)
+         break;
+       else if (*line == '\\')
+         escape ^= 1;
+       else
+         escape = 0;
+       line++;
+      }
+      append(start, line);
+      if (*line)
+       break;
+      if (!escape)
+       copy_buf.ptr[copied-1] = '\n';
+      else // merge two lines
+       copied -= 2;
+      if (!get_line())
+       return "Unterminated quoted word at the end";
+    }
+    line++;
+
+    byte *tmp = stk_str_unesc(copy_buf.ptr + start_copy);
+    uns l = strlen(tmp);
+    bb_grow(&copy_buf, start_copy + l + 1);
+    strcpy(copy_buf.ptr + start_copy, tmp);
+    copied = start_copy + l + 1;
+
+  } else {
+    // promised that *line is non-null and non-blank
+    byte *start = line;
+    while (*line && !Cblank(*line) && !CONTROL_CHAR(*line)
+       && (*line != '=' || !is_command_name))
+      line++;
+    if (*line == '=') {                                // nice for setting from a command-line
+      if (line == start)
+       return "Assignment without a variable";
+      *line = ' ';
+    }
+    if (line == start)                         // already the first char is control
+      line++;
+    append(start, line);
+  }
+  while (Cblank(*line))
+    line++;
+  return NULL;
+}
+
+static byte *
+get_token(uns is_command_name, byte **msg)
+{
+  *msg = NULL;
+  while (1) {
+    if (!*line || *line == '#') {
+      if (!is_command_name || !get_line())
+       return NULL;
+    } else if (*line == ';') {
+      *msg = get_word(0);
+      if (!is_command_name || *msg)
+       return NULL;
+    } else if (*line == '\\' && !line[1]) {
+      if (!get_line()) {
+       *msg = "Last line ends by a backslash";
+       return NULL;
+      }
+      if (!*line || *line == '#')
+       log(L_WARN, "The line %s:%d following a backslash is empty", name_parse_fb, line_num);
+    } else {
+      split_grow(&word_buf, words+1);
+      uns start = copied;
+      word_buf.ptr[words++] = copied;
+      *msg = get_word(is_command_name);
+      return *msg ? NULL : copy_buf.ptr + start;
+    }
+  }
+}
+
+static byte *
+split_command(void)
+{
+  words = copied = ends_by_brace = 0;
+  byte *msg, *start_word;
+  if (!(start_word = get_token(1, &msg)))
+    return msg;
+  if (*start_word == '{')                      // only one opening brace
+    return "Unexpected opening brace";
+  while (*line != '}')                         // stays for the next time
+  {
+    if (!(start_word = get_token(0, &msg)))
+      return msg;
+    if (*start_word == '{') {
+      words--;                                 // discard the brace
+      ends_by_brace = 1;
+      break;
+    }
+  }
+  return NULL;
+}
+
+/* Parsing multiple files */
+
+static byte *
+parse_fastbuf(byte *name_fb, struct fastbuf *fb, uns depth)
+{
+  byte *msg;
+  name_parse_fb = name_fb;
+  parse_fb = fb;
+  line_num = 0;
+  line = line_buf;
+  *line = 0;
+  while (1)
+  {
+    msg = split_command();
+    if (msg)
+      goto error;
+    if (!words)
+      return NULL;
+    byte *name = copy_buf.ptr + word_buf.ptr[0];
+    byte *pars[words-1];
+    for (uns i=1; i<words; i++)
+      pars[i-1] = copy_buf.ptr + word_buf.ptr[i];
+    if (!strcasecmp(name, "include"))
+    {
+      if (words != 2)
+       msg = "Expecting one filename";
+      else if (depth > 8)
+       msg = "Too many nested files";
+      else if (*line && *line != '#')          // because the contents of line_buf is not re-entrant and will be cleared
+       msg = "The input command must be the last one on a line";
+      if (msg)
+       goto error;
+      struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
+      if (!new_fb) {
+       msg = cf_printf("Cannot open file %s: %m", pars[0]);
+       goto error;
+      }
+      uns ll = line_num;
+      msg = parse_fastbuf(stk_strdup(pars[0]), new_fb, depth+1);
+      line_num = ll;
+      bclose(new_fb);
+      if (msg)
+       goto error;
+      parse_fb = fb;
+      continue;
+    }
+    enum cf_operation op;
+    byte *c = strchr(name, ':');
+    if (!c)
+      op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
+    else {
+      *c++ = 0;
+      switch (Clocase(*c)) {
+       case 's': op = OP_SET; break;
+       case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
+       case 'a': op = Clocase(c[1]) == 'p' ? OP_APPEND : OP_AFTER; break;
+       case 'p': op = OP_PREPEND; break;
+       case 'r': op = OP_REMOVE; break;
+       case 'e': op = OP_EDIT; break;
+       case 'b': op = OP_BEFORE; break;
+       default: op = OP_SET; break;
+      }
+      if (strcasecmp(c, cf_op_names[op])) {
+       msg = cf_printf("Unknown operation %s", c);
+       goto error;
+      }
+    }
+    if (ends_by_brace)
+      op |= OP_OPEN;
+    msg = cf_interpret_line(name, op, words-1, pars);
+    if (msg)
+      goto error;
+  }
+error:
+  log(L_ERROR, "File %s, line %d: %s", name_fb, line_num, msg);
+  return "included from here";
+}
+
+#ifndef DEFAULT_CONFIG
+#define DEFAULT_CONFIG NULL
+#endif
+byte *cf_def_file = DEFAULT_CONFIG;
+
+static uns postpone_commit;                    // only for cf_getopt()
+static uns everything_committed;               // after the 1st load, this flag is set on
+
+static int
+done_stack(void)
+{
+  if (cf_check_stack())
+    return 1;
+  if (cf_commit_all(postpone_commit ? CF_NO_COMMIT : everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
+    return 1;
+  if (!postpone_commit)
+    everything_committed = 1;
+  return 0;
+}
+
+static int
+load_file(byte *file)
+{
+  cf_init_stack();
+  struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
+  if (!fb) {
+    log(L_ERROR, "Cannot open %s: %m", file);
+    return 1;
+  }
+  byte *msg = parse_fastbuf(file, fb, 0);
+  bclose(fb);
+  int err = !!msg || done_stack();
+  if (!err)
+    cf_def_file = NULL;
+  return err;
+}
+
+static int
+load_string(byte *string)
+{
+  cf_init_stack();
+  struct fastbuf fb;
+  fbbuf_init_read(&fb, string, strlen(string), 0);
+  byte *msg = parse_fastbuf("memory string", &fb, 0);
+  return !!msg || done_stack();
+}
+
+/* Safe loading and reloading */
+
+int
+cf_reload(byte *file)
+{
+  cf_journal_swap();
+  struct cf_journal_item *oldj = cf_journal_new_transaction(1);
+  uns ec = everything_committed;
+  everything_committed = 0;
+  int err = load_file(file);
+  if (!err)
+  {
+    cf_journal_delete();
+    cf_journal_commit_transaction(1, NULL);
+  }
+  else
+  {
+    everything_committed = ec;
+    cf_journal_rollback_transaction(1, oldj);
+    cf_journal_swap();
+  }
+  return err;
+}
+
+int
+cf_load(byte *file)
+{
+  struct cf_journal_item *oldj = cf_journal_new_transaction(1);
+  int err = load_file(file);
+  if (!err)
+    cf_journal_commit_transaction(1, oldj);
+  else
+    cf_journal_rollback_transaction(1, oldj);
+  return err;
+}
+
+int
+cf_set(byte *string)
+{
+  struct cf_journal_item *oldj = cf_journal_new_transaction(0);
+  int err = load_string(string);
+  if (!err)
+    cf_journal_commit_transaction(0, oldj);
+  else
+    cf_journal_rollback_transaction(0, oldj);
+  return err;
+}
+
+/* Command-line parser */
+
+static void
+load_default(void)
+{
+  if (cf_def_file)
+    if (cf_load(cf_def_file))
+      die("Cannot load default config %s", cf_def_file);
+}
+
+static void
+final_commit(void)
+{
+  if (postpone_commit) {
+    postpone_commit = 0;
+    if (done_stack())
+      die("Cannot commit after the initialization");
+  }
+}
+
+int
+cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
+{
+  static int other_options = 0;
+  while (1) {
+    int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
+    if (res == 'S' || res == 'C' || res == 0x64436667)
+    {
+      if (other_options)
+       die("The -S and -C options must precede all other arguments");
+      if (res == 'S') {
+       postpone_commit = 1;
+       load_default();
+       if (cf_set(optarg))
+         die("Cannot set %s", optarg);
+      } else if (res == 'C') {
+       postpone_commit = 1;
+       if (cf_load(optarg))
+         die("Cannot load config file %s", optarg);
+      }
+#ifdef CONFIG_DEBUG
+      else {   /* --dumpconfig */
+       load_default();
+       final_commit();
+       struct fastbuf *b = bfdopen(1, 4096);
+       cf_dump_sections(b);
+       bclose(b);
+       exit(0);
+      }
+#endif
+    } else {
+      /* unhandled option or end of options */
+      if (res != ':' && res != '?')
+       load_default();
+      final_commit();
+      other_options++;
+      return res;
+    }
+  }
+}
+
diff --git a/lib/conf-internal.h b/lib/conf-internal.h
new file mode 100644 (file)
index 0000000..fbcdc03
--- /dev/null
@@ -0,0 +1,28 @@
+/* conf-intr.c */
+#define OP_MASK 0xff           // only get the operation
+#define OP_OPEN 0x100          // here we only get an opening brace instead of parameters
+#define OP_1ST 0x200           // in the 1st phase selectors are recorded into the mask
+#define OP_2ND 0x400           // in the 2nd phase real data are entered
+enum cf_operation;
+extern byte *cf_op_names[];
+
+uns cf_type_size(enum cf_type type, struct cf_user_type *utype);
+byte *cf_interpret_line(byte *name, enum cf_operation op, int number, byte **pars);
+void cf_init_stack(void);
+int cf_check_stack(void);
+
+/* conf-journal.c */
+void cf_journal_swap(void);
+void cf_journal_delete(void);
+
+/* conf-section.c */
+#define SEC_FLAG_DYNAMIC       0x80000000      // contains a dynamic attribute
+#define SEC_FLAG_UNKNOWN       0x40000000      // ignore unknown entriies
+#define SEC_FLAG_CANT_COPY     0x20000000      // contains lists or parsers
+#define SEC_FLAG_NUMBER                0x0fffffff      // number of entries
+enum cf_commit_mode { CF_NO_COMMIT, CF_COMMIT, CF_COMMIT_ALL };
+extern struct cf_section cf_sections;
+
+struct cf_item *cf_find_subitem(struct cf_section *sec, byte *name);
+int cf_commit_all(enum cf_commit_mode cm);
+void cf_add_dirty(struct cf_section *sec, void *ptr);
diff --git a/lib/conf-intr.c b/lib/conf-intr.c
new file mode 100644 (file)
index 0000000..5895bd6
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ *     UCW Library -- Configuration files: interpreter
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/getopt.h"
+#include "lib/conf-internal.h"
+#include "lib/clists.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#define TRY(f) do { byte *_msg = f; if (_msg) return _msg; } while (0)
+
+/* Register size of and parser for each basic type */
+
+static byte *
+cf_parse_string(byte *str, byte **ptr)
+{
+  *ptr = cf_strdup(str);
+  return NULL;
+}
+
+typedef byte *cf_basic_parser(byte *str, void *ptr);
+static struct {
+  uns size;
+  void *parser;
+} parsers[] = {
+  { sizeof(int), cf_parse_int },
+  { sizeof(u64), cf_parse_u64 },
+  { sizeof(double), cf_parse_double },
+  { sizeof(u32), cf_parse_ip },
+  { sizeof(byte*), cf_parse_string },
+  { sizeof(int), NULL },                       // lookups are parsed extra
+  { 0, NULL },                                 // user-defined types are parsed extra
+};
+
+inline uns
+cf_type_size(enum cf_type type, struct cf_user_type *utype)
+{
+  if (type < CT_USER)
+    return parsers[type].size;
+  else
+    return utype->size;
+}
+
+static byte *
+cf_parse_lookup(byte *str, int *ptr, byte **t)
+{
+  byte **n = t;
+  uns total_len = 0;
+  while (*n && strcasecmp(*n, str)) {
+    total_len += strlen(*n) + 2;
+    n++;
+  }
+  if (*n) {
+    *ptr = n - t;
+    return NULL;
+  }
+  byte *err = cf_malloc(total_len + strlen(str) + 60), *c = err;
+  c += sprintf(err, "Invalid value %s, possible values are: ", str);
+  for (n=t; *n; n++)
+    c+= sprintf(c, "%s, ", *n);
+  if (*t)
+    c[-2] = 0;
+  *ptr = -1;
+  return err;
+}
+
+static byte *
+cf_parse_ary(uns number, byte **pars, void *ptr, enum cf_type type, union cf_union *u)
+{
+  for (uns i=0; i<number; i++)
+  {
+    byte *msg;
+    uns size = cf_type_size(type, u->utype);
+    if (type < CT_LOOKUP)
+      msg = ((cf_basic_parser*) parsers[type].parser) (pars[i], ptr + i * size);
+    else if (type == CT_LOOKUP)
+      msg = cf_parse_lookup(pars[i], ptr + i * size, u->lookup);
+    else if (type == CT_USER)
+      msg = u->utype->parser(pars[i], ptr + i * size);
+    else
+      ASSERT(0);
+    if (msg)
+      return cf_printf("Cannot parse item %d: %s", i+1, msg);
+  }
+  return NULL;
+}
+
+/* Interpreter */
+
+#define T(x) #x,
+byte *cf_op_names[] = { CF_OPERATIONS };
+#undef T
+
+static byte *
+interpret_set_dynamic(struct cf_item *item, int number, byte **pars, void **ptr)
+{
+  enum cf_type type = item->type;
+  cf_journal_block(ptr, sizeof(void*));
+  // boundary checks done by the caller
+  uns size = cf_type_size(item->type, item->u.utype);
+  ASSERT(size >= sizeof(uns));
+  *ptr = cf_malloc((number+1) * size) + size;
+  * (uns*) (*ptr - size) = number;
+  return cf_parse_ary(number, pars, *ptr, type, &item->u);
+}
+
+static byte *
+interpret_add_dynamic(struct cf_item *item, int number, byte **pars, int *processed, void **ptr, enum cf_operation op)
+{
+  enum cf_type type = item->type;
+  void *old_p = *ptr;
+  uns size = cf_type_size(item->type, item->u.utype);
+  ASSERT(size >= sizeof(uns));
+  int old_nr = old_p ? * (int*) (old_p - size) : 0;
+  int taken = MIN(number, ABS(item->number)-old_nr);
+  *processed = taken;
+  // stretch the dynamic array
+  void *new_p = cf_malloc((old_nr + taken + 1) * size) + size;
+  * (uns*) (new_p - size) = old_nr + taken;
+  cf_journal_block(ptr, sizeof(void*));
+  *ptr = new_p;
+  if (op == OP_APPEND) {
+    memcpy(new_p, old_p, old_nr * size);
+    return cf_parse_ary(taken, pars, new_p + old_nr * size, type, &item->u);
+  } else if (op == OP_PREPEND) {
+    memcpy(new_p + taken * size, old_p, old_nr * size);
+    return cf_parse_ary(taken, pars, new_p, type, &item->u);
+  } else
+    return cf_printf("Dynamic arrays do not support operation %s", cf_op_names[op]);
+}
+
+static byte *interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic);
+
+static byte *
+interpret_section(struct cf_section *sec, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
+{
+  cf_add_dirty(sec, ptr);
+  *processed = 0;
+  for (struct cf_item *ci=sec->cfg; ci->cls; ci++)
+  {
+    int taken;
+    byte *msg = interpret_set_item(ci, number, pars, &taken, ptr + (addr_int_t) ci->ptr, allow_dynamic && !ci[1].cls);
+    if (msg)
+      return cf_printf("Item %s: %s", ci->name, msg);
+    *processed += taken;
+    number -= taken;
+    pars += taken;
+    if (!number)               // stop parsing, because many parsers would otherwise complain that number==0
+      break;
+  }
+  return NULL;
+}
+
+static void
+add_to_list(cnode *where, cnode *new_node, enum cf_operation op)
+{
+  switch (op)
+  {
+    case OP_EDIT:              // edition has been done in-place
+      break;
+    case OP_REMOVE:
+      CF_JOURNAL_VAR(where->prev->next);
+      CF_JOURNAL_VAR(where->next->prev);
+      clist_remove(where);
+      break;
+    case OP_AFTER:             // implementation dependend (prepend_head = after(list)), and where==list, see clists.h:74
+    case OP_PREPEND:
+    case OP_COPY:
+      CF_JOURNAL_VAR(where->next->prev);
+      CF_JOURNAL_VAR(where->next);
+      clist_insert_after(new_node, where);
+      break;
+    case OP_BEFORE:            // implementation dependend (append_tail = before(list))
+    case OP_APPEND:
+    case OP_SET:
+      CF_JOURNAL_VAR(where->prev->next);
+      CF_JOURNAL_VAR(where->prev);
+      clist_insert_before(new_node, where);
+      break;
+    default:
+      ASSERT(0);
+  }
+}
+
+static byte *
+interpret_add_list(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, enum cf_operation op)
+{
+  if (op >= OP_REMOVE)
+    return cf_printf("You have to open a block for operation %s", cf_op_names[op]);
+  if (!number)
+    return "Nothing to add to the list";
+  struct cf_section *sec = item->u.sec;
+  *processed = 0;
+  while (number > 0)
+  {
+    void *node = cf_malloc(sec->size);
+    cf_init_section(item->name, sec, node, 1);
+    add_to_list(ptr, node, op);
+    int taken;
+    /* If the node contains any dynamic attribute at the end, we suppress
+     * auto-repetition here and pass the flag inside instead.  */
+    TRY( interpret_section(sec, number, pars, &taken, node, sec->flags & SEC_FLAG_DYNAMIC) );
+    *processed += taken;
+    number -= taken;
+    pars += taken;
+    if (sec->flags & SEC_FLAG_DYNAMIC)
+      break;
+  }
+  return NULL;
+}
+
+static byte *
+interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
+{
+  int taken;
+  switch (item->cls)
+  {
+    case CC_STATIC:
+      if (!number)
+       return "Missing value";
+      taken = MIN(number, item->number);
+      *processed = taken;
+      uns size = cf_type_size(item->type, item->u.utype);
+      cf_journal_block(ptr, taken * size);
+      return cf_parse_ary(taken, pars, ptr, item->type, &item->u);
+    case CC_DYNAMIC:
+      if (!allow_dynamic)
+       return "Dynamic array cannot be used here";
+      taken = MIN(number, ABS(item->number));
+      *processed = taken;
+      return interpret_set_dynamic(item, taken, pars, ptr);
+    case CC_PARSER:
+      if (item->number < 0 && !allow_dynamic)
+       return "Parsers with variable number of parameters cannot be used here";
+      if (item->number > 0 && number < item->number)
+       return "Not enough parameters available for the parser";
+      taken = MIN(number, ABS(item->number));
+      *processed = taken;
+      for (int i=0; i<taken; i++)
+       pars[i] = cf_strdup(pars[i]);
+      return item->u.par(taken, pars, ptr);
+    case CC_SECTION:
+      return interpret_section(item->u.sec, number, pars, processed, ptr, allow_dynamic);
+    case CC_LIST:
+      if (!allow_dynamic)
+       return "Lists cannot be used here";
+      return interpret_add_list(item, number, pars, processed, ptr, OP_SET);
+    default:
+      ASSERT(0);
+  }
+}
+
+static byte *
+interpret_clear(struct cf_item *item, void *ptr)
+{
+  if (item->cls == CC_LIST) {
+    cf_journal_block(ptr, sizeof(clist));
+    clist_init(ptr);
+  } else if (item->cls == CC_DYNAMIC) {
+    cf_journal_block(ptr, sizeof(void *));
+    * (void**) ptr = NULL;
+  } else
+    return "The item is not a list or a dynamic array";
+  return NULL;
+}
+
+static int
+cmp_items(void *i1, void *i2, struct cf_item *item)
+{
+  ASSERT(item->cls == CC_STATIC);
+  i1 += (addr_int_t) item->ptr;
+  i2 += (addr_int_t) item->ptr;
+  if (item->type == CT_STRING)
+    return strcmp(* (byte**) i1, * (byte**) i2);
+  else                         // all numeric types
+    return memcmp(i1, i2, cf_type_size(item->type, item->u.utype));
+}
+
+static void *
+find_list_node(clist *list, void *query, struct cf_section *sec, u32 mask)
+{
+  CLIST_FOR_EACH(cnode *, n, *list)
+  {
+    uns found = 1;
+    for (uns i=0; i<32; i++)
+      if (mask & (1<<i))
+       if (cmp_items(n, query, sec->cfg+i))
+       {
+         found = 0;
+         break;
+       }
+    if (found)
+      return n;
+  }
+  return NULL;
+}
+
+static byte *
+record_selector(struct cf_item *item, struct cf_section *sec, u32 *mask)
+{
+  uns nr = sec->flags & SEC_FLAG_NUMBER;
+  if (item >= sec->cfg && item < sec->cfg + nr)        // setting an attribute relative to this section
+  {
+    uns i = item - sec->cfg;
+    if (i >= 32)
+      return "Cannot select list nodes by this attribute";
+    if (sec->cfg[i].cls != CC_STATIC)
+      return "Selection can only be done based on basic attributes";
+    *mask |= 1 << i;
+  }
+  return NULL;
+}
+
+#define MAX_STACK_SIZE 10
+static struct item_stack {
+  struct cf_section *sec;      // nested section
+  void *base_ptr;              // because original pointers are often relative
+  enum cf_operation op;                // it is performed when a closing brace is encountered
+  void *list;                  // list the operations should be done on
+  u32 mask;                    // bit array of selectors searching in a list
+  struct cf_item *item;                // cf_item of the list
+} stack[MAX_STACK_SIZE];
+static uns level;
+
+static byte *
+opening_brace(struct cf_item *item, void *ptr, enum cf_operation op)
+{
+  if (level >= MAX_STACK_SIZE-1)
+    return "Too many nested sections";
+  stack[++level] = (struct item_stack) {
+    .sec = NULL,
+    .base_ptr = NULL,
+    .op = op & OP_MASK,
+    .list = NULL,
+    .mask = 0,
+    .item = NULL,
+  };
+  if (!item)                   // unknown is ignored; we just need to trace recursion
+    return NULL;
+  stack[level].sec = item->u.sec;
+  if (item->cls == CC_SECTION)
+  {
+    stack[level].base_ptr = ptr;
+    stack[level].op = OP_EDIT | OP_2ND;        // this list operation does nothing
+  }
+  else if (item->cls == CC_LIST)
+  {
+    stack[level].base_ptr = cf_malloc(item->u.sec->size);
+    cf_init_section(item->name, item->u.sec, stack[level].base_ptr, 1);
+    stack[level].list = ptr;
+    stack[level].item = item;
+    if ((op & OP_MASK) < OP_REMOVE) {
+      add_to_list(ptr, stack[level].base_ptr, op & OP_MASK);
+      stack[level].op |= OP_2ND;
+    } else
+      stack[level].op |= OP_1ST;
+  }
+  else
+    return "Opening brace can only be used on sections and lists";
+  return NULL;
+}
+
+static byte *
+closing_brace(struct item_stack *st, enum cf_operation op, int number, byte **pars)
+{
+  if (st->op == OP_CLOSE)      // top-level
+    return "Unmatched } parenthesis";
+  if (!st->sec) {              // dummy run on unknown section
+    if (!(op & OP_OPEN))
+      level--;
+    return NULL;
+  }
+  enum cf_operation pure_op = st->op & OP_MASK;
+  if (st->op & OP_1ST)
+  {
+    st->list = find_list_node(st->list, st->base_ptr, st->sec, st->mask);
+    if (!st->list)
+      return "Cannot find a node matching the query";
+    if (pure_op != OP_REMOVE)
+    {
+      if (pure_op == OP_EDIT)
+       st->base_ptr = st->list;
+      else if (pure_op == OP_AFTER || pure_op == OP_BEFORE)
+       cf_init_section(st->item->name, st->sec, st->base_ptr, 1);
+      else if (pure_op == OP_COPY) {
+       if (st->sec->flags & SEC_FLAG_CANT_COPY)
+         return cf_printf("Item %s cannot be copied", st->item->name);
+       memcpy(st->base_ptr, st->list, st->sec->size);  // strings and dynamic arrays are shared
+       if (st->sec->copy)
+         TRY( st->sec->copy(st->base_ptr, st->list) );
+      } else
+       ASSERT(0);
+      if (op & OP_OPEN) {      // stay at the same recursion level
+       st->op = (st->op | OP_2ND) & ~OP_1ST;
+       add_to_list(st->list, st->base_ptr, pure_op);
+       return NULL;
+      }
+      int taken;               // parse parameters on 1 line immediately
+      TRY( interpret_section(st->sec, number, pars, &taken, st->base_ptr, 1) );
+      number -= taken;
+      pars += taken;
+      // and fall-thru to the 2nd phase
+    }
+    add_to_list(st->list, st->base_ptr, pure_op);
+  }
+  level--;
+  if (number)
+    return "No parameters expected after the }";
+  else if (op & OP_OPEN)
+    return "No { is expected";
+  else
+    return NULL;
+}
+
+static struct cf_item *
+find_item(struct cf_section *curr_sec, byte *name, byte **msg, void **ptr)
+{
+  *msg = NULL;
+  if (name[0] == '^')                          // absolute name instead of relative
+    name++, curr_sec = &cf_sections, *ptr = NULL;
+  if (!curr_sec)                               // don't even search in an unknown section
+    return NULL;
+  while (1)
+  {
+    if (curr_sec != &cf_sections)
+      cf_add_dirty(curr_sec, *ptr);
+    byte *c = strchr(name, '.');
+    if (c)
+      *c++ = 0;
+    struct cf_item *ci = cf_find_subitem(curr_sec, name);
+    if (!ci->cls)
+    {
+      if (!(curr_sec->flags & SEC_FLAG_UNKNOWN))       // ignore silently unknown top-level sections and unknown attributes in flagged sections
+       *msg = cf_printf("Unknown item %s", name);
+      return NULL;
+    }
+    *ptr += (addr_int_t) ci->ptr;
+    if (!c)
+      return ci;
+    if (ci->cls != CC_SECTION)
+    {
+      *msg = cf_printf("Item %s is not a section", name);
+      return NULL;
+    }
+    curr_sec = ci->u.sec;
+    name = c;
+  }
+}
+
+byte *
+cf_interpret_line(byte *name, enum cf_operation op, int number, byte **pars)
+{
+  byte *msg;
+  if ((op & OP_MASK) == OP_CLOSE)
+    return closing_brace(stack+level, op, number, pars);
+  void *ptr = stack[level].base_ptr;
+  struct cf_item *item = find_item(stack[level].sec, name, &msg, &ptr);
+  if (msg)
+    return msg;
+  if (stack[level].op & OP_1ST)
+    TRY( record_selector(item, stack[level].sec, &stack[level].mask) );
+  if (op & OP_OPEN) {          // the operation will be performed after the closing brace
+    if (number)
+      return "Cannot open a block after a parameter has been passed on a line";
+    return opening_brace(item, ptr, op);
+  }
+  if (!item)                   // ignored item in an unknown section
+    return NULL;
+  op &= OP_MASK;
+
+  int taken;                   // process as many parameters as possible
+  if (op == OP_CLEAR)
+    taken = 0, msg = interpret_clear(item, ptr);
+  else if (op == OP_SET)
+    msg = interpret_set_item(item, number, pars, &taken, ptr, 1);
+  else if (item->cls == CC_DYNAMIC)
+    msg = interpret_add_dynamic(item, number, pars, &taken, ptr, op);
+  else if (item->cls == CC_LIST)
+    msg = interpret_add_list(item, number, pars, &taken, ptr, op);
+  else
+    return cf_printf("Operation %s not supported on attribute %s", cf_op_names[op], name);
+  if (msg)
+    return msg;
+  if (taken < number)
+    return cf_printf("Too many parameters: %d>%d", number, taken);
+
+  return NULL;
+}
+
+byte *
+cf_find_item(byte *name, struct cf_item *item)
+{
+  byte *msg;
+  void *ptr;
+  struct cf_item *ci = find_item(&cf_sections, name, &msg, &ptr);
+  if (msg)
+    return msg;
+  if (ci) {
+    *item = *ci;
+    item->ptr = ptr;
+  } else
+    bzero(item, sizeof(struct cf_item));
+  return NULL;
+}
+
+byte *
+cf_write_item(struct cf_item *item, enum cf_operation op, int number, byte **pars)
+{
+  byte *msg;
+  int taken;
+  switch (op) {
+    case OP_SET:
+      msg = interpret_set_item(item, number, pars, &taken, item->ptr, 1);
+      break;
+    case OP_CLEAR:
+      taken = 0;
+      msg = interpret_clear(item, item->ptr);
+      break;
+    case OP_APPEND:
+    case OP_PREPEND:
+      if (item->cls == CC_DYNAMIC)
+       msg = interpret_add_dynamic(item, number, pars, &taken, item->ptr, op);
+      else if (item->cls == CC_LIST)
+       msg = interpret_add_list(item, number, pars, &taken, item->ptr, op);
+      else
+       return "The attribute class does not support append/prepend";
+      break;
+    default:
+      return "Unsupported operation";
+  }
+  if (msg)
+    return msg;
+  if (taken < number)
+    return "Too many parameters";
+  return NULL;
+}
+
+void
+cf_init_stack(void)
+{
+  static uns initialized = 0;
+  if (!initialized++) {
+    cf_sections.flags |= SEC_FLAG_UNKNOWN;
+    cf_sections.size = 0;                      // size of allocated array used to be stored here
+    cf_init_section(NULL, &cf_sections, NULL, 0);
+  }
+  level = 0;
+  stack[0] = (struct item_stack) {
+    .sec = &cf_sections,
+    .base_ptr = NULL,
+    .op = OP_CLOSE,
+    .list = NULL,
+    .mask = 0,
+    .item = NULL
+  };
+}
+
+int
+cf_check_stack(void)
+{
+  if (level > 0) {
+    log(L_ERROR, "Unterminated block");
+    return 1;
+  }
+  return 0;
+}
+
diff --git a/lib/conf-journal.c b/lib/conf-journal.c
new file mode 100644 (file)
index 0000000..a28c228
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ *     UCW Library -- Configuration files: journaling
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/conf-internal.h"
+#include "lib/mempool.h"
+
+#include <string.h>
+
+static struct old_pools {
+  struct old_pools *prev;
+  struct mempool *pool;
+} *pools;                      // link-list of older cf_pool's
+
+uns cf_need_journal = 1;       // some programs do not need journal
+static struct cf_journal_item {
+  struct cf_journal_item *prev;
+  byte *ptr;
+  uns len;
+  byte copy[0];
+} *journal;
+
+void
+cf_journal_block(void *ptr, uns len)
+{
+  if (!cf_need_journal)
+    return;
+  struct cf_journal_item *ji = cf_malloc(sizeof(struct cf_journal_item) + len);
+  ji->prev = journal;
+  ji->ptr = ptr;
+  ji->len = len;
+  memcpy(ji->copy, ptr, len);
+  journal = ji;
+}
+
+void
+cf_journal_swap(void)
+  // swaps the contents of the memory and the journal, and reverses the list
+{
+  struct cf_journal_item *curr, *prev, *next;
+  for (next=NULL, curr=journal; curr; next=curr, curr=prev)
+  {
+    prev = curr->prev;
+    curr->prev = next;
+    for (uns i=0; i<curr->len; i++)
+    {
+      byte x = curr->copy[i];
+      curr->copy[i] = curr->ptr[i];
+      curr->ptr[i] = x;
+    }
+  }
+  journal = next;
+}
+
+struct cf_journal_item *
+cf_journal_new_transaction(uns new_pool)
+{
+  if (new_pool)
+    cf_pool = mp_new(1<<10);
+  struct cf_journal_item *oldj = journal;
+  journal = NULL;
+  return oldj;
+}
+
+void
+cf_journal_commit_transaction(uns new_pool, struct cf_journal_item *oldj)
+{
+  if (new_pool)
+  {
+    struct old_pools *p = cf_malloc(sizeof(struct old_pools));
+    p->prev = pools;
+    p->pool = cf_pool;
+    pools = p;
+  }
+  if (oldj)
+  {
+    struct cf_journal_item **j = &journal;
+    while (*j)
+      j = &(*j)->prev;
+    *j = oldj;
+  }
+}
+
+void
+cf_journal_rollback_transaction(uns new_pool, struct cf_journal_item *oldj)
+{
+  if (!cf_need_journal)
+    die("Cannot rollback the configuration, because the journal is disabled.");
+  cf_journal_swap();
+  journal = oldj;
+  if (new_pool)
+  {
+    mp_delete(cf_pool);
+    cf_pool = pools ? pools->pool : NULL;
+  }
+}
+
+void
+cf_journal_delete(void)
+{
+  for (struct old_pools *p=pools; p; p=pools)
+  {
+    pools = p->prev;
+    mp_delete(p->pool);
+  }
+}
+
+/* TODO: more space efficient journal */
diff --git a/lib/conf-parse.c b/lib/conf-parse.c
new file mode 100644 (file)
index 0000000..9a82f9c
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ *     UCW Library -- Configuration files: parsers for basic types
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/chartype.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+struct unit {
+  uns name;                    // one-letter name of the unit
+  uns num, den;                        // fraction
+};
+
+static const struct unit units[] = {
+  { 'd', 86400, 1 },
+  { 'h', 3600, 1 },
+  { 'k', 1000, 1 },
+  { 'm', 1000000, 1 },
+  { 'g', 1000000000, 1 },
+  { 'K', 1024, 1 },
+  { 'M', 1048576, 1 },
+  { 'G', 1073741824, 1 },
+  { '%', 1, 100 },
+  { 0, 0, 0 }
+};
+
+static const struct unit *
+lookup_unit(byte *value, byte *end, byte **msg)
+{
+  if (end && *end) {
+    if (end == value || end[1] || *end >= '0' && *end <= '9')
+      *msg = "Invalid number";
+    else {
+      for (const struct unit *u=units; u->name; u++)
+       if (u->name == *end)
+         return u;
+      *msg = "Invalid unit";
+    }
+  }
+  return NULL;
+}
+
+static char cf_rngerr[] = "Number out of range";
+
+byte *
+cf_parse_int(byte *str, int *ptr)
+{
+  byte *msg = NULL;
+  if (!*str)
+    msg = "Missing number";
+  else {
+    const struct unit *u;
+    char *end;
+    errno = 0;
+    uns x = strtoul(str, &end, 0);
+    if (errno == ERANGE)
+      msg = cf_rngerr;
+    else if (u = lookup_unit(str, end, &msg)) {
+      u64 y = (u64)x * u->num;
+      if (y % u->den)
+       msg = "Number is not an integer";
+      else {
+       y /= u->den;
+       if (y > 0xffffffff)
+         msg = cf_rngerr;
+       *ptr = y;
+      }
+    } else
+      *ptr = x;
+  }
+  return msg;
+}
+
+byte *
+cf_parse_u64(byte *str, u64 *ptr)
+{
+  byte *msg = NULL;
+  if (!*str)
+    msg = "Missing number";
+  else {
+    const struct unit *u;
+    char *end;
+    errno = 0;
+    u64 x = strtoull(str, &end, 0);
+    if (errno == ERANGE)
+      msg = cf_rngerr;
+    else if (u = lookup_unit(str, end, &msg)) {
+      if (x > ~(u64)0 / u->num)
+       msg = "Number out of range";
+      else {
+       x *= u->num;
+       if (x % u->den)
+         msg = "Number is not an integer";
+       else
+         *ptr = x / u->den;
+      }
+    } else
+      *ptr = x;
+  }
+  return msg;
+}
+
+byte *
+cf_parse_double(byte *str, double *ptr)
+{
+  byte *msg = NULL;
+  if (!*str)
+    msg = "Missing number";
+  else {
+    const struct unit *u;
+    double x;
+    uns read_chars;
+    if (sscanf(str, "%lf%n", &x, &read_chars) != 1)
+      msg = "Invalid number";
+    else if (u = lookup_unit(str, str + read_chars, &msg))
+      *ptr = x * u->num / u->den;
+    else
+      *ptr = x;
+  }
+  return msg;
+}
+
+byte *
+cf_parse_ip(byte *p, u32 *varp)
+{
+  if (!*p)
+    return "Missing IP address";
+  uns x = 0;
+  char *p2;
+  if (*p == '0' && (p[1] | 32) == 'x' && Cxdigit(p[2])) {
+    errno = 0;
+    x = strtoul(p, &p2, 16);
+    if (errno == ERANGE || x > 0xffffffff)
+      goto error;
+    p = p2;
+  }
+  else
+    for (uns i = 0; i < 4; i++) {
+      if (i) {
+       if (*p++ != '.')
+         goto error;
+      }
+      if (!Cdigit(*p))
+       goto error;
+      errno = 0;
+      uns y = strtoul(p, &p2, 10);
+      if (errno == ERANGE || p2 == (char*) p || y > 255)
+       goto error;
+      p = p2;
+      x = (x << 8) + y;
+    }
+  *varp = x;
+  return *p ? "Trailing characters" : NULL;
+error:
+  return "Invalid IP address";
+}
+
diff --git a/lib/conf-section.c b/lib/conf-section.c
new file mode 100644 (file)
index 0000000..590dec9
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ *     UCW Library -- Configuration files: sections
+ *
+ *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
+ *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include "lib/lib.h"
+#include "lib/conf.h"
+#include "lib/conf-internal.h"
+#include "lib/clists.h"
+#include "lib/binsearch.h"
+
+#include <string.h>
+
+#define TRY(f) do { byte *_msg = f; if (_msg) return _msg; } while (0)
+
+/* Dirty sections */
+
+struct dirty_section {
+  struct cf_section *sec;
+  void *ptr;
+};
+#define GBUF_TYPE      struct dirty_section
+#define GBUF_PREFIX(x) dirtsec_##x
+#include "lib/gbuf.h"
+static dirtsec_t dirty;
+static uns dirties;
+
+void
+cf_add_dirty(struct cf_section *sec, void *ptr)
+{
+  dirtsec_grow(&dirty, dirties+1);
+  struct dirty_section *dest = dirty.ptr + dirties;
+  if (dirties && dest[-1].sec == sec && dest[-1].ptr == ptr)
+    return;
+  dest->sec = sec;
+  dest->ptr = ptr;
+  dirties++;
+}
+
+#define ASORT_PREFIX(x)        dirtsec_##x
+#define ASORT_KEY_TYPE struct dirty_section
+#define ASORT_ELT(i)   dirty.ptr[i]
+#define ASORT_LT(x,y)  x.sec < y.sec || x.sec == y.sec && x.ptr < y.ptr
+#include "lib/arraysort.h"
+
+static void
+sort_dirty(void)
+{
+  if (dirties <= 1)
+    return;
+  dirtsec_sort(dirties);
+  // and compress the list
+  struct dirty_section *read = dirty.ptr + 1, *write = dirty.ptr + 1, *limit = dirty.ptr + dirties;
+  while (read < limit) {
+    if (read->sec != read[-1].sec || read->ptr != read[-1].ptr) {
+      if (read != write)
+       *write = *read;
+      write++;
+    }
+    read++;
+  }
+  dirties = write - dirty.ptr;
+}
+
+/* Initialization */
+
+struct cf_section cf_sections; // root section
+
+struct cf_item *
+cf_find_subitem(struct cf_section *sec, byte *name)
+{
+  struct cf_item *ci = sec->cfg;
+  for (; ci->cls; ci++)
+    if (!strcasecmp(ci->name, name))
+      return ci;
+  return ci;
+}
+
+static void
+inspect_section(struct cf_section *sec)
+{
+  sec->flags = 0;
+  struct cf_item *ci;
+  for (ci=sec->cfg; ci->cls; ci++)
+    if (ci->cls == CC_SECTION) {
+      inspect_section(ci->u.sec);
+      sec->flags |= ci->u.sec->flags & (SEC_FLAG_DYNAMIC | SEC_FLAG_CANT_COPY);
+    } else if (ci->cls == CC_LIST) {
+      inspect_section(ci->u.sec);
+      sec->flags |= SEC_FLAG_DYNAMIC | SEC_FLAG_CANT_COPY;
+    } else if (ci->cls == CC_DYNAMIC)
+      sec->flags |= SEC_FLAG_DYNAMIC;
+    else if (ci->cls == CC_PARSER) {
+      sec->flags |= SEC_FLAG_CANT_COPY;
+      if (ci->number < 0)
+       sec->flags |= SEC_FLAG_DYNAMIC;
+    }
+  if (sec->copy)
+    sec->flags &= ~SEC_FLAG_CANT_COPY;
+  sec->flags |= ci - sec->cfg;         // record the number of entries
+}
+
+void
+cf_declare_section(byte *name, struct cf_section *sec, uns allow_unknown)
+{
+  if (!cf_sections.cfg)
+  {
+    cf_sections.size = 50;
+    cf_sections.cfg = xmalloc_zero(cf_sections.size * sizeof(struct cf_item));
+  }
+  struct cf_item *ci = cf_find_subitem(&cf_sections, name);
+  if (ci->cls)
+    die("Cannot register section %s twice", name);
+  ci->cls = CC_SECTION;
+  ci->name = name;
+  ci->number = 1;
+  ci->ptr = NULL;
+  ci->u.sec = sec;
+  inspect_section(sec);
+  if (allow_unknown)
+    sec->flags |= SEC_FLAG_UNKNOWN;
+  ci++;
+  if (ci - cf_sections.cfg >= (int) cf_sections.size)
+  {
+    cf_sections.cfg = xrealloc(cf_sections.cfg, 2*cf_sections.size * sizeof(struct cf_item));
+    bzero(cf_sections.cfg + cf_sections.size, cf_sections.size * sizeof(struct cf_item));
+    cf_sections.size *= 2;
+  }
+}
+
+void
+cf_init_section(byte *name, struct cf_section *sec, void *ptr, uns do_bzero)
+{
+  if (do_bzero) {
+    ASSERT(sec->size);
+    bzero(ptr, sec->size);
+  }
+  for (uns i=0; sec->cfg[i].cls; i++)
+    if (sec->cfg[i].cls == CC_SECTION)
+      cf_init_section(sec->cfg[i].name, sec->cfg[i].u.sec, ptr + (addr_int_t) sec->cfg[i].ptr, 0);
+    else if (sec->cfg[i].cls == CC_LIST)
+      clist_init(ptr + (addr_int_t) sec->cfg[i].ptr);
+  if (sec->init) {
+    byte *msg = sec->init(ptr);
+    if (msg)
+      die("Cannot initialize section %s: %s", name, msg);
+  }
+}
+
+static byte *
+commit_section(struct cf_section *sec, void *ptr, uns commit_all)
+{
+  struct cf_item *ci;
+  byte *err;
+  for (ci=sec->cfg; ci->cls; ci++)
+    if (ci->cls == CC_SECTION) {
+      if ((err = commit_section(ci->u.sec, ptr + (addr_int_t) ci->ptr, commit_all))) {
+       log(L_ERROR, "Cannot commit section %s: %s", ci->name, err);
+       return "commit of a subsection failed";
+      }
+    } else if (ci->cls == CC_LIST) {
+      uns idx = 0;
+      CLIST_FOR_EACH(cnode *, n, * (clist*) (ptr + (addr_int_t) ci->ptr))
+       if (idx++, err = commit_section(ci->u.sec, n, commit_all)) {
+         log(L_ERROR, "Cannot commit node #%d of list %s: %s", idx, ci->name, err);
+         return "commit of a list failed";
+       }
+    }
+  if (sec->commit) {
+    /* We have to process the whole tree of sections even if just a few changes
+     * have been made, because there are dependencies between commit-hooks and
+     * hence we need to call them in a fixed order.  */
+#define ARY_LT_X(ary,i,x) ary[i].sec < x.sec || ary[i].sec == x.sec && ary[i].ptr < x.ptr
+    struct dirty_section comp = { sec, ptr };
+    uns pos = BIN_SEARCH_FIRST_GE_CMP(dirty.ptr, dirties, comp, ARY_LT_X);
+
+    if (commit_all
+       || (pos < dirties && dirty.ptr[pos].sec == sec && dirty.ptr[pos].ptr == ptr)) {
+      return sec->commit(ptr);
+    }
+  }
+  return 0;
+}
+
+int
+cf_commit_all(enum cf_commit_mode cm)
+{
+  sort_dirty();
+  if (cm == CF_NO_COMMIT)
+    return 0;
+  if (commit_section(&cf_sections, NULL, cm == CF_COMMIT_ALL))
+    return 1;
+  dirties = 0;
+  return 0;
+}
index 458689de858fb2b740a5d006b07aecdfd170221e..89e65bd309572debddc173c18699ed426393bc6e 100644 (file)
@@ -164,7 +164,7 @@ static struct option long_opts[] = {
 };
 
 static char *help = "\
-Usage: conf2-test <options>\n\
+Usage: conf-test <options>\n\
 \n\
 Options:\n"
 CF_USAGE
@@ -187,7 +187,7 @@ main(int argc, char *argv[])
 {
   log_init(argv[0]);
   cf_declare_section("top", &cf_top, 0);
-  cf_def_file = "lib/conf2.t";
+  cf_def_file = "lib/conf-test.t";
 
   int opt;
   while ((opt = cf_getopt(argc, argv, short_opts, long_opts, NULL)) >= 0)
diff --git a/lib/conf-test.t b/lib/conf-test.t
new file mode 100644 (file)
index 0000000..39b803b
--- /dev/null
@@ -0,0 +1,48 @@
+# test config file
+#include lib/conf-test.t ; top.xa=1
+#include 'non-existent file'; #top.xa=1
+Top { \
+
+  nr1=16       #!!!
+  nrs1         2 3 5 \
+           7 11 13 \
+           \
+           17M
+  nrs2 3 3k 3 3 3 ; \
+  str1 "hello,\t\x2bworld%%\n"
+  str2 'Hagenuk,
+      the best' "\
+      " qu'est-ce que c'est?
+  u1   0xbadcafebadbeefc0
+  str2:prepend prepended
+  str2:append appended
+  d1 7%
+  d1   -1.14e-25
+  firsttime ; secondtime 56
+  ^top.master:set      alice HB8+
+  slaves:clear
+  ip 0xa
+  ip 195.113.31.123
+  look Alpha
+  look:prepend Beta GAMMA
+  numbers 11000 65535
+};;;;;;
+
+unknown.ignored :-)
+
+top.slaves     cairns gpua 7 7 -10% +10%
+top.slaves     daintree rafc 4 5 -171%
+top.slaves     coogee pum 9 8
+top.slaves:prepend     {name=bondi; level=\
+  "PUG"; confidence    10 10}
+top.slaves:remove {name daintree}
+top.slaveS:edit {level PUG} Bondi PUG!
+top.slaveS:before {level pum}{
+  confidence 2
+  list 123 456 789
+}
+top.slaves:copy {name coogee} Coogee2 PUM
+
+topp.a=15
+top.nr1=   ' 15'
+a { ;-D }
diff --git a/lib/conf2.c b/lib/conf2.c
deleted file mode 100644 (file)
index a8f2446..0000000
+++ /dev/null
@@ -1,1545 +0,0 @@
-/*
- *     UCW Library -- Reading of configuration files
- *
- *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
- *     (c) 2003--2006 Martin Mares <mj@ucw.cz>
- *
- *     This software may be freely distributed and used according to the terms
- *     of the GNU Lesser General Public License.
- */
-
-#include "lib/lib.h"
-#include "lib/conf.h"
-#include "lib/getopt.h"
-#include "lib/mempool.h"
-#include "lib/clists.h"
-#include "lib/fastbuf.h"
-#include "lib/chartype.h"
-#include "lib/lfs.h"
-#include "lib/stkstring.h"
-#include "lib/binsearch.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <fcntl.h>
-
-#define TRY(f) do { byte *_msg = f; if (_msg) return _msg; } while (0)
-
-/* Memory allocation */
-
-struct mempool *cf_pool;       // current pool for loading new configuration
-static struct old_pools {
-  struct old_pools *prev;
-  struct mempool *pool;
-} *pools;                      // link-list of older cf_pool's
-
-void *
-cf_malloc(uns size)
-{
-  return mp_alloc(cf_pool, size);
-}
-
-void *
-cf_malloc_zero(uns size)
-{
-  return mp_alloc_zero(cf_pool, size);
-}
-
-byte *
-cf_strdup(byte *s)
-{
-  return mp_strdup(cf_pool, s);
-}
-
-byte *
-cf_printf(char *fmt, ...)
-{
-  va_list args;
-  va_start(args, fmt);
-  byte *res = mp_vprintf(cf_pool, fmt, args);
-  va_end(args);
-  return res;
-}
-
-/* Undo journal */
-
-uns cf_need_journal = 1;       // some programs do not need journal
-static struct cf_journal_item {
-  struct cf_journal_item *prev;
-  byte *ptr;
-  uns len;
-  byte copy[0];
-} *journal;
-
-void
-cf_journal_block(void *ptr, uns len)
-{
-  if (!cf_need_journal)
-    return;
-  struct cf_journal_item *ji = cf_malloc(sizeof(struct cf_journal_item) + len);
-  ji->prev = journal;
-  ji->ptr = ptr;
-  ji->len = len;
-  memcpy(ji->copy, ptr, len);
-  journal = ji;
-}
-
-static void
-journal_swap(void)
-  // swaps the contents of the memory and the journal, and reverses the list
-{
-  struct cf_journal_item *curr, *prev, *next;
-  for (next=NULL, curr=journal; curr; next=curr, curr=prev)
-  {
-    prev = curr->prev;
-    curr->prev = next;
-    for (uns i=0; i<curr->len; i++)
-    {
-      byte x = curr->copy[i];
-      curr->copy[i] = curr->ptr[i];
-      curr->ptr[i] = x;
-    }
-  }
-  journal = next;
-}
-
-struct cf_journal_item *
-cf_journal_new_transaction(uns new_pool)
-{
-  if (new_pool)
-    cf_pool = mp_new(1<<10);
-  struct cf_journal_item *oldj = journal;
-  journal = NULL;
-  return oldj;
-}
-
-void
-cf_journal_commit_transaction(uns new_pool, struct cf_journal_item *oldj)
-{
-  if (new_pool)
-  {
-    struct old_pools *p = cf_malloc(sizeof(struct old_pools));
-    p->prev = pools;
-    p->pool = cf_pool;
-    pools = p;
-  }
-  if (oldj)
-  {
-    struct cf_journal_item **j = &journal;
-    while (*j)
-      j = &(*j)->prev;
-    *j = oldj;
-  }
-}
-
-void
-cf_journal_rollback_transaction(uns new_pool, struct cf_journal_item *oldj)
-{
-  if (!cf_need_journal)
-    die("Cannot rollback the configuration, because the journal is disabled.");
-  journal_swap();
-  journal = oldj;
-  if (new_pool)
-  {
-    mp_delete(cf_pool);
-    cf_pool = pools ? pools->pool : NULL;
-  }
-}
-
-/* Dirty sections */
-
-struct dirty_section {
-  struct cf_section *sec;
-  void *ptr;
-};
-#define GBUF_TYPE      struct dirty_section
-#define GBUF_PREFIX(x) dirtsec_##x
-#include "lib/gbuf.h"
-static dirtsec_t dirty;
-static uns dirties;
-static uns everything_committed;               // after the 1st load, this flag is set on
-
-static void
-add_dirty(struct cf_section *sec, void *ptr)
-{
-  dirtsec_grow(&dirty, dirties+1);
-  struct dirty_section *dest = dirty.ptr + dirties;
-  if (dirties && dest[-1].sec == sec && dest[-1].ptr == ptr)
-    return;
-  dest->sec = sec;
-  dest->ptr = ptr;
-  dirties++;
-}
-
-#define ASORT_PREFIX(x)        dirtsec_##x
-#define ASORT_KEY_TYPE struct dirty_section
-#define ASORT_ELT(i)   dirty.ptr[i]
-#define ASORT_LT(x,y)  x.sec < y.sec || x.sec == y.sec && x.ptr < y.ptr
-#include "lib/arraysort.h"
-
-static void
-sort_dirty(void)
-{
-  if (dirties <= 1)
-    return;
-  dirtsec_sort(dirties);
-  struct dirty_section *read = dirty.ptr + 1, *write = dirty.ptr + 1, *limit = dirty.ptr + dirties;
-  while (read < limit) {
-    if (read->sec != read[-1].sec || read->ptr != read[-1].ptr) {
-      if (read != write)
-       *write = *read;
-      write++;
-    }
-    read++;
-  }
-  dirties = write - dirty.ptr;
-}
-
-/* Initialization */
-
-#define SEC_FLAG_DYNAMIC       0x80000000      // contains a dynamic attribute
-#define SEC_FLAG_UNKNOWN       0x40000000      // ignore unknown entriies
-#define SEC_FLAG_CANT_COPY     0x20000000      // contains lists or parsers
-#define SEC_FLAG_NUMBER                0x0fffffff      // number of entries
-
-static struct cf_section sections;     // root section
-
-static struct cf_item *
-find_subitem(struct cf_section *sec, byte *name)
-{
-  struct cf_item *ci = sec->cfg;
-  for (; ci->cls; ci++)
-    if (!strcasecmp(ci->name, name))
-      return ci;
-  return ci;
-}
-
-static void
-inspect_section(struct cf_section *sec)
-{
-  sec->flags = 0;
-  struct cf_item *ci;
-  for (ci=sec->cfg; ci->cls; ci++)
-    if (ci->cls == CC_SECTION) {
-      inspect_section(ci->u.sec);
-      sec->flags |= ci->u.sec->flags & (SEC_FLAG_DYNAMIC | SEC_FLAG_CANT_COPY);
-    } else if (ci->cls == CC_LIST) {
-      inspect_section(ci->u.sec);
-      sec->flags |= SEC_FLAG_DYNAMIC | SEC_FLAG_CANT_COPY;
-    } else if (ci->cls == CC_DYNAMIC)
-      sec->flags |= SEC_FLAG_DYNAMIC;
-    else if (ci->cls == CC_PARSER) {
-      sec->flags |= SEC_FLAG_CANT_COPY;
-      if (ci->number < 0)
-       sec->flags |= SEC_FLAG_DYNAMIC;
-    }
-  if (sec->copy)
-    sec->flags &= ~SEC_FLAG_CANT_COPY;
-  sec->flags |= ci - sec->cfg;         // record the number of entries
-}
-
-void
-cf_declare_section(byte *name, struct cf_section *sec, uns allow_unknown)
-{
-  if (!sections.cfg)
-  {
-    sections.size = 50;
-    sections.cfg = xmalloc_zero(sections.size * sizeof(struct cf_item));
-  }
-  struct cf_item *ci = find_subitem(&sections, name);
-  if (ci->cls)
-    die("Cannot register section %s twice", name);
-  ci->cls = CC_SECTION;
-  ci->name = name;
-  ci->number = 1;
-  ci->ptr = NULL;
-  ci->u.sec = sec;
-  inspect_section(sec);
-  if (allow_unknown)
-    sec->flags |= SEC_FLAG_UNKNOWN;
-  ci++;
-  if (ci - sections.cfg >= (int) sections.size)
-  {
-    sections.cfg = xrealloc(sections.cfg, 2*sections.size * sizeof(struct cf_item));
-    bzero(sections.cfg + sections.size, sections.size * sizeof(struct cf_item));
-    sections.size *= 2;
-  }
-}
-
-void
-cf_init_section(byte *name, struct cf_section *sec, void *ptr, uns do_bzero)
-{
-  if (do_bzero) {
-    ASSERT(sec->size);
-    bzero(ptr, sec->size);
-  }
-  for (uns i=0; sec->cfg[i].cls; i++)
-    if (sec->cfg[i].cls == CC_SECTION)
-      cf_init_section(sec->cfg[i].name, sec->cfg[i].u.sec, ptr + (addr_int_t) sec->cfg[i].ptr, 0);
-    else if (sec->cfg[i].cls == CC_LIST)
-      clist_init(ptr + (addr_int_t) sec->cfg[i].ptr);
-  if (sec->init) {
-    byte *msg = sec->init(ptr);
-    if (msg)
-      die("Cannot initialize section %s: %s", name, msg);
-  }
-}
-
-static void
-global_init(void)
-{
-  static uns initialized = 0;
-  if (initialized++)
-    return;
-  sections.flags |= SEC_FLAG_UNKNOWN;
-  sections.size = 0;                   // size of allocated array used to be stored here
-  cf_init_section(NULL, &sections, NULL, 0);
-}
-
-static byte *
-commit_section(struct cf_section *sec, void *ptr, uns commit_all)
-{
-  struct cf_item *ci;
-  byte *err;
-  for (ci=sec->cfg; ci->cls; ci++)
-    if (ci->cls == CC_SECTION) {
-      if ((err = commit_section(ci->u.sec, ptr + (addr_int_t) ci->ptr, commit_all))) {
-       log(L_ERROR, "Cannot commit section %s: %s", ci->name, err);
-       return "commit of a subsection failed";
-      }
-    } else if (ci->cls == CC_LIST) {
-      uns idx = 0;
-      CLIST_FOR_EACH(cnode *, n, * (clist*) (ptr + (addr_int_t) ci->ptr))
-       if (idx++, err = commit_section(ci->u.sec, n, commit_all)) {
-         log(L_ERROR, "Cannot commit node #%d of list %s: %s", idx, ci->name, err);
-         return "commit of a list failed";
-       }
-    }
-  if (sec->commit) {
-    /* We have to process the whole tree of sections even if just a few changes
-     * have been made, because there are dependencies between commit-hooks and
-     * hence we need to call them in a fixed order.  */
-#define ARY_LT_X(ary,i,x) ary[i].sec < x.sec || ary[i].sec == x.sec && ary[i].ptr < x.ptr
-    struct dirty_section comp = { sec, ptr };
-    uns pos = BIN_SEARCH_FIRST_GE_CMP(dirty.ptr, dirties, comp, ARY_LT_X);
-
-    if (commit_all
-       || (pos < dirties && dirty.ptr[pos].sec == sec && dirty.ptr[pos].ptr == ptr)) {
-      return sec->commit(ptr);
-    }
-  }
-  return 0;
-}
-
-static struct cf_item *
-find_item(struct cf_section *curr_sec, byte *name, byte **msg, void **ptr)
-{
-  *msg = NULL;
-  if (name[0] == '^')                          // absolute name instead of relative
-    name++, curr_sec = &sections, *ptr = NULL;
-  if (!curr_sec)                               // don't even search in an unknown section
-    return NULL;
-  while (1)
-  {
-    if (curr_sec != &sections)
-      add_dirty(curr_sec, *ptr);
-    byte *c = strchr(name, '.');
-    if (c)
-      *c++ = 0;
-    struct cf_item *ci = find_subitem(curr_sec, name);
-    if (!ci->cls)
-    {
-      if (!(curr_sec->flags & SEC_FLAG_UNKNOWN))       // ignore silently unknown top-level sections and unknown attributes in flagged sections
-       *msg = cf_printf("Unknown item %s", name);
-      return NULL;
-    }
-    *ptr += (addr_int_t) ci->ptr;
-    if (!c)
-      return ci;
-    if (ci->cls != CC_SECTION)
-    {
-      *msg = cf_printf("Item %s is not a section", name);
-      return NULL;
-    }
-    curr_sec = ci->u.sec;
-    name = c;
-  }
-}
-
-byte *
-cf_find_item(byte *name, struct cf_item *item)
-{
-  byte *msg;
-  void *ptr;
-  struct cf_item *ci = find_item(&sections, name, &msg, &ptr);
-  if (msg)
-    return msg;
-  if (ci) {
-    *item = *ci;
-    item->ptr = ptr;
-  } else
-    bzero(item, sizeof(struct cf_item));
-  return NULL;
-}
-
-/* Safe loading and reloading */
-
-static int load_file(byte *file);
-static int load_string(byte *string);
-
-int
-cf_reload(byte *file)
-{
-  journal_swap();
-  struct cf_journal_item *oldj = cf_journal_new_transaction(1);
-  uns ec = everything_committed;
-  everything_committed = 0;
-  int err = load_file(file);
-  if (!err)
-  {
-    for (struct old_pools *p=pools; p; p=pools)
-    {
-      pools = p->prev;
-      mp_delete(p->pool);
-    }
-    cf_journal_commit_transaction(1, NULL);
-  }
-  else
-  {
-    everything_committed = ec;
-    cf_journal_rollback_transaction(1, oldj);
-    journal_swap();
-  }
-  return err;
-}
-
-int
-cf_load(byte *file)
-{
-  struct cf_journal_item *oldj = cf_journal_new_transaction(1);
-  int err = load_file(file);
-  if (!err)
-    cf_journal_commit_transaction(1, oldj);
-  else
-    cf_journal_rollback_transaction(1, oldj);
-  return err;
-}
-
-int
-cf_set(byte *string)
-{
-  struct cf_journal_item *oldj = cf_journal_new_transaction(0);
-  int err = load_string(string);
-  if (!err)
-    cf_journal_commit_transaction(0, oldj);
-  else
-    cf_journal_rollback_transaction(0, oldj);
-  return err;
-}
-
-/* Parsers for standard types */
-
-struct unit {
-  uns name;                    // one-letter name of the unit
-  uns num, den;                        // fraction
-};
-
-static const struct unit units[] = {
-  { 'd', 86400, 1 },
-  { 'h', 3600, 1 },
-  { 'k', 1000, 1 },
-  { 'm', 1000000, 1 },
-  { 'g', 1000000000, 1 },
-  { 'K', 1024, 1 },
-  { 'M', 1048576, 1 },
-  { 'G', 1073741824, 1 },
-  { '%', 1, 100 },
-  { 0, 0, 0 }
-};
-
-static const struct unit *
-lookup_unit(byte *value, byte *end, byte **msg)
-{
-  if (end && *end) {
-    if (end == value || end[1] || *end >= '0' && *end <= '9')
-      *msg = "Invalid number";
-    else {
-      for (const struct unit *u=units; u->name; u++)
-       if (u->name == *end)
-         return u;
-      *msg = "Invalid unit";
-    }
-  }
-  return NULL;
-}
-
-static char cf_rngerr[] = "Number out of range";
-
-byte *
-cf_parse_int(byte *str, int *ptr)
-{
-  byte *msg = NULL;
-  if (!*str)
-    msg = "Missing number";
-  else {
-    const struct unit *u;
-    char *end;
-    errno = 0;
-    uns x = strtoul(str, &end, 0);
-    if (errno == ERANGE)
-      msg = cf_rngerr;
-    else if (u = lookup_unit(str, end, &msg)) {
-      u64 y = (u64)x * u->num;
-      if (y % u->den)
-       msg = "Number is not an integer";
-      else {
-       y /= u->den;
-       if (y > 0xffffffff)
-         msg = cf_rngerr;
-       *ptr = y;
-      }
-    } else
-      *ptr = x;
-  }
-  return msg;
-}
-
-byte *
-cf_parse_u64(byte *str, u64 *ptr)
-{
-  byte *msg = NULL;
-  if (!*str)
-    msg = "Missing number";
-  else {
-    const struct unit *u;
-    char *end;
-    errno = 0;
-    u64 x = strtoull(str, &end, 0);
-    if (errno == ERANGE)
-      msg = cf_rngerr;
-    else if (u = lookup_unit(str, end, &msg)) {
-      if (x > ~(u64)0 / u->num)
-       msg = "Number out of range";
-      else {
-       x *= u->num;
-       if (x % u->den)
-         msg = "Number is not an integer";
-       else
-         *ptr = x / u->den;
-      }
-    } else
-      *ptr = x;
-  }
-  return msg;
-}
-
-byte *
-cf_parse_double(byte *str, double *ptr)
-{
-  byte *msg = NULL;
-  if (!*str)
-    msg = "Missing number";
-  else {
-    const struct unit *u;
-    double x;
-    uns read_chars;
-    if (sscanf(str, "%lf%n", &x, &read_chars) != 1)
-      msg = "Invalid number";
-    else if (u = lookup_unit(str, str + read_chars, &msg))
-      *ptr = x * u->num / u->den;
-    else
-      *ptr = x;
-  }
-  return msg;
-}
-
-byte *
-cf_parse_ip(byte *p, u32 *varp)
-{
-  if (!*p)
-    return "Missing IP address";
-  uns x = 0;
-  char *p2;
-  if (*p == '0' && (p[1] | 32) == 'x' && Cxdigit(p[2])) {
-    errno = 0;
-    x = strtoul(p, &p2, 16);
-    if (errno == ERANGE || x > 0xffffffff)
-      goto error;
-    p = p2;
-  }
-  else
-    for (uns i = 0; i < 4; i++) {
-      if (i) {
-       if (*p++ != '.')
-         goto error;
-      }
-      if (!Cdigit(*p))
-       goto error;
-      errno = 0;
-      uns y = strtoul(p, &p2, 10);
-      if (errno == ERANGE || p2 == (char*) p || y > 255)
-       goto error;
-      p = p2;
-      x = (x << 8) + y;
-    }
-  *varp = x;
-  return *p ? "Trailing characters" : NULL;
-error:
-  return "Invalid IP address";
-}
-
-static byte *
-cf_parse_string(byte *str, byte **ptr)
-{
-  *ptr = cf_strdup(str);
-  return NULL;
-}
-
-static byte *
-cf_parse_lookup(byte *str, int *ptr, byte **t)
-{
-  byte **n = t;
-  uns total_len = 0;
-  while (*n && strcasecmp(*n, str)) {
-    total_len += strlen(*n) + 2;
-    n++;
-  }
-  if (*n) {
-    *ptr = n - t;
-    return NULL;
-  }
-  byte *err = cf_malloc(total_len + strlen(str) + 60), *c = err;
-  c += sprintf(err, "Invalid value %s, possible values are: ", str);
-  for (n=t; *n; n++)
-    c+= sprintf(c, "%s, ", *n);
-  if (*t)
-    c[-2] = 0;
-  *ptr = -1;
-  return err;
-}
-
-/* Register size of and parser for each basic type */
-
-typedef byte *cf_basic_parser(byte *str, void *ptr);
-static struct {
-  uns size;
-  void *parser;
-} parsers[] = {
-  { sizeof(int), cf_parse_int },
-  { sizeof(u64), cf_parse_u64 },
-  { sizeof(double), cf_parse_double },
-  { sizeof(u32), cf_parse_ip },
-  { sizeof(byte*), cf_parse_string },
-  { sizeof(int), NULL },                       // lookups are parsed extra
-  { 0, NULL },                                 // user-defined types are parsed extra
-};
-
-static inline uns
-type_size(enum cf_type type, struct cf_user_type *utype)
-{
-  if (type < CT_USER)
-    return parsers[type].size;
-  else
-    return utype->size;
-}
-
-static byte *
-cf_parse_ary(uns number, byte **pars, void *ptr, enum cf_type type, union cf_union *u)
-{
-  for (uns i=0; i<number; i++)
-  {
-    byte *msg;
-    uns size = type_size(type, u->utype);
-    if (type < CT_LOOKUP)
-      msg = ((cf_basic_parser*) parsers[type].parser) (pars[i], ptr + i * size);
-    else if (type == CT_LOOKUP)
-      msg = cf_parse_lookup(pars[i], ptr + i * size, u->lookup);
-    else if (type == CT_USER)
-      msg = u->utype->parser(pars[i], ptr + i * size);
-    else
-      ASSERT(0);
-    if (msg)
-      return cf_printf("Cannot parse item %d: %s", i+1, msg);
-  }
-  return NULL;
-}
-
-/* Interpreter */
-
-#define T(x) #x,
-static byte *op_names[] = { CF_OPERATIONS };
-#undef T
-
-#define OP_MASK 0xff           // only get the operation
-#define OP_OPEN 0x100          // here we only get an opening brace instead of parameters
-#define OP_1ST 0x200           // in the 1st phase selectors are recorded into the mask
-#define OP_2ND 0x400           // in the 2nd phase real data are entered
-
-static byte *
-interpret_set_dynamic(struct cf_item *item, int number, byte **pars, void **ptr)
-{
-  enum cf_type type = item->type;
-  cf_journal_block(ptr, sizeof(void*));
-  // boundary checks done by the caller
-  uns size = type_size(item->type, item->u.utype);
-  ASSERT(size >= sizeof(uns));
-  *ptr = cf_malloc((number+1) * size) + size;
-  * (uns*) (*ptr - size) = number;
-  return cf_parse_ary(number, pars, *ptr, type, &item->u);
-}
-
-static byte *
-interpret_add_dynamic(struct cf_item *item, int number, byte **pars, int *processed, void **ptr, enum cf_operation op)
-{
-  enum cf_type type = item->type;
-  void *old_p = *ptr;
-  uns size = type_size(item->type, item->u.utype);
-  ASSERT(size >= sizeof(uns));
-  int old_nr = old_p ? * (int*) (old_p - size) : 0;
-  int taken = MIN(number, ABS(item->number)-old_nr);
-  *processed = taken;
-  // stretch the dynamic array
-  void *new_p = cf_malloc((old_nr + taken + 1) * size) + size;
-  * (uns*) (new_p - size) = old_nr + taken;
-  cf_journal_block(ptr, sizeof(void*));
-  *ptr = new_p;
-  if (op == OP_APPEND) {
-    memcpy(new_p, old_p, old_nr * size);
-    return cf_parse_ary(taken, pars, new_p + old_nr * size, type, &item->u);
-  } else if (op == OP_PREPEND) {
-    memcpy(new_p + taken * size, old_p, old_nr * size);
-    return cf_parse_ary(taken, pars, new_p, type, &item->u);
-  } else
-    return cf_printf("Dynamic arrays do not support operation %s", op_names[op]);
-}
-
-static byte *interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic);
-
-static byte *
-interpret_section(struct cf_section *sec, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
-{
-  add_dirty(sec, ptr);
-  *processed = 0;
-  for (struct cf_item *ci=sec->cfg; ci->cls; ci++)
-  {
-    int taken;
-    byte *msg = interpret_set_item(ci, number, pars, &taken, ptr + (addr_int_t) ci->ptr, allow_dynamic && !ci[1].cls);
-    if (msg)
-      return cf_printf("Item %s: %s", ci->name, msg);
-    *processed += taken;
-    number -= taken;
-    pars += taken;
-    if (!number)               // stop parsing, because many parsers would otherwise complain that number==0
-      break;
-  }
-  return NULL;
-}
-
-static void
-add_to_list(cnode *where, cnode *new_node, enum cf_operation op)
-{
-  switch (op)
-  {
-    case OP_EDIT:              // edition has been done in-place
-      break;
-    case OP_REMOVE:
-      CF_JOURNAL_VAR(where->prev->next);
-      CF_JOURNAL_VAR(where->next->prev);
-      clist_remove(where);
-      break;
-    case OP_AFTER:             // implementation dependend (prepend_head = after(list)), and where==list, see clists.h:74
-    case OP_PREPEND:
-    case OP_COPY:
-      CF_JOURNAL_VAR(where->next->prev);
-      CF_JOURNAL_VAR(where->next);
-      clist_insert_after(new_node, where);
-      break;
-    case OP_BEFORE:            // implementation dependend (append_tail = before(list))
-    case OP_APPEND:
-    case OP_SET:
-      CF_JOURNAL_VAR(where->prev->next);
-      CF_JOURNAL_VAR(where->prev);
-      clist_insert_before(new_node, where);
-      break;
-    default:
-      ASSERT(0);
-  }
-}
-
-static byte *
-interpret_add_list(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, enum cf_operation op)
-{
-  if (op >= OP_REMOVE)
-    return cf_printf("You have to open a block for operation %s", op_names[op]);
-  if (!number)
-    return "Nothing to add to the list";
-  struct cf_section *sec = item->u.sec;
-  *processed = 0;
-  while (number > 0)
-  {
-    void *node = cf_malloc(sec->size);
-    cf_init_section(item->name, sec, node, 1);
-    add_to_list(ptr, node, op);
-    int taken;
-    /* If the node contains any dynamic attribute at the end, we suppress
-     * auto-repetition here and pass the flag inside instead.  */
-    TRY( interpret_section(sec, number, pars, &taken, node, sec->flags & SEC_FLAG_DYNAMIC) );
-    *processed += taken;
-    number -= taken;
-    pars += taken;
-    if (sec->flags & SEC_FLAG_DYNAMIC)
-      break;
-  }
-  return NULL;
-}
-
-static byte *
-interpret_set_item(struct cf_item *item, int number, byte **pars, int *processed, void *ptr, uns allow_dynamic)
-{
-  int taken;
-  switch (item->cls)
-  {
-    case CC_STATIC:
-      if (!number)
-       return "Missing value";
-      taken = MIN(number, item->number);
-      *processed = taken;
-      uns size = type_size(item->type, item->u.utype);
-      cf_journal_block(ptr, taken * size);
-      return cf_parse_ary(taken, pars, ptr, item->type, &item->u);
-    case CC_DYNAMIC:
-      if (!allow_dynamic)
-       return "Dynamic array cannot be used here";
-      taken = MIN(number, ABS(item->number));
-      *processed = taken;
-      return interpret_set_dynamic(item, taken, pars, ptr);
-    case CC_PARSER:
-      if (item->number < 0 && !allow_dynamic)
-       return "Parsers with variable number of parameters cannot be used here";
-      if (item->number > 0 && number < item->number)
-       return "Not enough parameters available for the parser";
-      taken = MIN(number, ABS(item->number));
-      *processed = taken;
-      for (int i=0; i<taken; i++)
-       pars[i] = cf_strdup(pars[i]);
-      return item->u.par(taken, pars, ptr);
-    case CC_SECTION:
-      return interpret_section(item->u.sec, number, pars, processed, ptr, allow_dynamic);
-    case CC_LIST:
-      if (!allow_dynamic)
-       return "Lists cannot be used here";
-      return interpret_add_list(item, number, pars, processed, ptr, OP_SET);
-    default:
-      ASSERT(0);
-  }
-}
-
-static byte *
-interpret_clear(struct cf_item *item, void *ptr)
-{
-  if (item->cls == CC_LIST) {
-    cf_journal_block(ptr, sizeof(clist));
-    clist_init(ptr);
-  } else if (item->cls == CC_DYNAMIC) {
-    cf_journal_block(ptr, sizeof(void *));
-    * (void**) ptr = NULL;
-  } else
-    return "The item is not a list or a dynamic array";
-  return NULL;
-}
-
-static int
-cmp_items(void *i1, void *i2, struct cf_item *item)
-{
-  ASSERT(item->cls == CC_STATIC);
-  i1 += (addr_int_t) item->ptr;
-  i2 += (addr_int_t) item->ptr;
-  if (item->type == CT_STRING)
-    return strcmp(* (byte**) i1, * (byte**) i2);
-  else                         // all numeric types
-    return memcmp(i1, i2, type_size(item->type, item->u.utype));
-}
-
-static void *
-find_list_node(clist *list, void *query, struct cf_section *sec, u32 mask)
-{
-  CLIST_FOR_EACH(cnode *, n, *list)
-  {
-    uns found = 1;
-    for (uns i=0; i<32; i++)
-      if (mask & (1<<i))
-       if (cmp_items(n, query, sec->cfg+i))
-       {
-         found = 0;
-         break;
-       }
-    if (found)
-      return n;
-  }
-  return NULL;
-}
-
-static byte *
-record_selector(struct cf_item *item, struct cf_section *sec, u32 *mask)
-{
-  uns nr = sec->flags & SEC_FLAG_NUMBER;
-  if (item >= sec->cfg && item < sec->cfg + nr)        // setting an attribute relative to this section
-  {
-    uns i = item - sec->cfg;
-    if (i >= 32)
-      return "Cannot select list nodes by this attribute";
-    if (sec->cfg[i].cls != CC_STATIC)
-      return "Selection can only be done based on basic attributes";
-    *mask |= 1 << i;
-  }
-  return NULL;
-}
-
-#define MAX_STACK_SIZE 100
-static struct item_stack {
-  struct cf_section *sec;      // nested section
-  void *base_ptr;              // because original pointers are often relative
-  enum cf_operation op;                // it is performed when a closing brace is encountered
-  void *list;                  // list the operations should be done on
-  u32 mask;                    // bit array of selectors searching in a list
-  struct cf_item *item;                // cf_item of the list
-} stack[MAX_STACK_SIZE];
-static uns level;
-
-static byte *
-opening_brace(struct cf_item *item, void *ptr, enum cf_operation op)
-{
-  if (level >= MAX_STACK_SIZE-1)
-    return "Too many nested sections";
-  stack[++level] = (struct item_stack) {
-    .sec = NULL,
-    .base_ptr = NULL,
-    .op = op & OP_MASK,
-    .list = NULL,
-    .mask = 0,
-    .item = NULL,
-  };
-  if (!item)                   // unknown is ignored; we just need to trace recursion
-    return NULL;
-  stack[level].sec = item->u.sec;
-  if (item->cls == CC_SECTION)
-  {
-    stack[level].base_ptr = ptr;
-    stack[level].op = OP_EDIT | OP_2ND;        // this list operation does nothing
-  }
-  else if (item->cls == CC_LIST)
-  {
-    stack[level].base_ptr = cf_malloc(item->u.sec->size);
-    cf_init_section(item->name, item->u.sec, stack[level].base_ptr, 1);
-    stack[level].list = ptr;
-    stack[level].item = item;
-    if ((op & OP_MASK) < OP_REMOVE) {
-      add_to_list(ptr, stack[level].base_ptr, op & OP_MASK);
-      stack[level].op |= OP_2ND;
-    } else
-      stack[level].op |= OP_1ST;
-  }
-  else
-    return "Opening brace can only be used on sections and lists";
-  return NULL;
-}
-
-static byte *
-closing_brace(struct item_stack *st, enum cf_operation op, int number, byte **pars)
-{
-  if (st->op == OP_CLOSE)      // top-level
-    return "Unmatched } parenthesis";
-  if (!st->sec) {              // dummy run on unknown section
-    if (!(op & OP_OPEN))
-      level--;
-    return NULL;
-  }
-  enum cf_operation pure_op = st->op & OP_MASK;
-  if (st->op & OP_1ST)
-  {
-    st->list = find_list_node(st->list, st->base_ptr, st->sec, st->mask);
-    if (!st->list)
-      return "Cannot find a node matching the query";
-    if (pure_op != OP_REMOVE)
-    {
-      if (pure_op == OP_EDIT)
-       st->base_ptr = st->list;
-      else if (pure_op == OP_AFTER || pure_op == OP_BEFORE)
-       cf_init_section(st->item->name, st->sec, st->base_ptr, 1);
-      else if (pure_op == OP_COPY) {
-       if (st->sec->flags & SEC_FLAG_CANT_COPY)
-         return cf_printf("Item %s cannot be copied", st->item->name);
-       memcpy(st->base_ptr, st->list, st->sec->size);  // strings and dynamic arrays are shared
-       if (st->sec->copy)
-         TRY( st->sec->copy(st->base_ptr, st->list) );
-      } else
-       ASSERT(0);
-      if (op & OP_OPEN) {      // stay at the same recursion level
-       st->op = (st->op | OP_2ND) & ~OP_1ST;
-       add_to_list(st->list, st->base_ptr, pure_op);
-       return NULL;
-      }
-      int taken;               // parse parameters on 1 line immediately
-      TRY( interpret_section(st->sec, number, pars, &taken, st->base_ptr, 1) );
-      number -= taken;
-      pars += taken;
-      // and fall-thru to the 2nd phase
-    }
-    add_to_list(st->list, st->base_ptr, pure_op);
-  }
-  level--;
-  if (number)
-    return "No parameters expected after the }";
-  else if (op & OP_OPEN)
-    return "No { is expected";
-  else
-    return NULL;
-}
-
-static byte *
-interpret_line(byte *name, enum cf_operation op, int number, byte **pars)
-{
-  byte *msg;
-  if ((op & OP_MASK) == OP_CLOSE)
-    return closing_brace(stack+level, op, number, pars);
-  void *ptr = stack[level].base_ptr;
-  struct cf_item *item = find_item(stack[level].sec, name, &msg, &ptr);
-  if (msg)
-    return msg;
-  if (stack[level].op & OP_1ST)
-    TRY( record_selector(item, stack[level].sec, &stack[level].mask) );
-  if (op & OP_OPEN) {          // the operation will be performed after the closing brace
-    if (number)
-      return "Cannot open a block after a parameter has been passed on a line";
-    return opening_brace(item, ptr, op);
-  }
-  if (!item)                   // ignored item in an unknown section
-    return NULL;
-  op &= OP_MASK;
-
-  int taken;                   // process as many parameters as possible
-  if (op == OP_CLEAR)
-    taken = 0, msg = interpret_clear(item, ptr);
-  else if (op == OP_SET)
-    msg = interpret_set_item(item, number, pars, &taken, ptr, 1);
-  else if (item->cls == CC_DYNAMIC)
-    msg = interpret_add_dynamic(item, number, pars, &taken, ptr, op);
-  else if (item->cls == CC_LIST)
-    msg = interpret_add_list(item, number, pars, &taken, ptr, op);
-  else
-    return cf_printf("Operation %s not supported on attribute %s", op_names[op], name);
-  if (msg)
-    return msg;
-  if (taken < number)
-    return cf_printf("Too many parameters: %d>%d", number, taken);
-
-  return NULL;
-}
-
-byte *
-cf_write_item(struct cf_item *item, enum cf_operation op, int number, byte **pars)
-{
-  byte *msg;
-  int taken;
-  switch (op) {
-    case OP_SET:
-      msg = interpret_set_item(item, number, pars, &taken, item->ptr, 1);
-      break;
-    case OP_CLEAR:
-      taken = 0;
-      msg = interpret_clear(item, item->ptr);
-      break;
-    case OP_APPEND:
-    case OP_PREPEND:
-      if (item->cls == CC_DYNAMIC)
-       msg = interpret_add_dynamic(item, number, pars, &taken, item->ptr, op);
-      else if (item->cls == CC_LIST)
-       msg = interpret_add_list(item, number, pars, &taken, item->ptr, op);
-      else
-       return "The attribute class does not support append/prepend";
-      break;
-    default:
-      return "Unsupported operation";
-  }
-  if (msg)
-    return msg;
-  if (taken < number)
-    return "Too many parameters";
-  return NULL;
-}
-
-static void
-init_stack(void)
-{
-  global_init();
-  level = 0;
-  stack[0] = (struct item_stack) {
-    .sec = &sections,
-    .base_ptr = NULL,
-    .op = OP_CLOSE,
-    .list = NULL,
-    .mask = 0,
-    .item = NULL
-  };
-}
-
-static uns postpone_commit;                    // only for cf_getopt()
-
-static int
-done_stack(void)
-{
-  if (level > 0) {
-    log(L_ERROR, "Unterminated block");
-    return 1;
-  }
-  sort_dirty();
-  if (postpone_commit)
-    return 0;
-  if (commit_section(&sections, NULL, !everything_committed))
-    return 1;
-  everything_committed = 1;
-  dirties = 0;
-  return 0;
-}
-
-static void
-final_commit(void)
-{
-  if (postpone_commit) {
-    postpone_commit = 0;
-    if (done_stack())
-      die("Cannot commit after the initialization");
-  }
-}
-
-/* Text file parser */
-
-static byte *name_parse_fb;
-static struct fastbuf *parse_fb;
-static uns line_num;
-
-#define MAX_LINE       4096
-static byte line_buf[MAX_LINE];
-static byte *line = line_buf;
-
-#include "lib/bbuf.h"
-static bb_t copy_buf;
-static uns copied;
-
-#define GBUF_TYPE      uns
-#define GBUF_PREFIX(x) split_##x
-#include "lib/gbuf.h"
-static split_t word_buf;
-static uns words;
-static uns ends_by_brace;              // the line is ended by "{"
-
-static int
-get_line(void)
-{
-  if (!bgets(parse_fb, line_buf, MAX_LINE))
-    return 0;
-  line_num++;
-  line = line_buf;
-  while (Cblank(*line))
-    line++;
-  return 1;
-}
-
-static void
-append(byte *start, byte *end)
-{
-  uns len = end - start;
-  bb_grow(&copy_buf, copied + len + 1);
-  memcpy(copy_buf.ptr + copied, start, len);
-  copied += len + 1;
-  copy_buf.ptr[copied-1] = 0;
-}
-
-#define        CONTROL_CHAR(x) (x == '{' || x == '}' || x == ';')
-  // these characters separate words like blanks
-
-static byte *
-get_word(uns is_command_name)
-{
-  if (*line == '\'') {
-    line++;
-    while (1) {
-      byte *start = line;
-      while (*line && *line != '\'')
-       line++;
-      append(start, line);
-      if (*line)
-       break;
-      copy_buf.ptr[copied-1] = '\n';
-      if (!get_line())
-       return "Unterminated apostrophe word at the end";
-    }
-    line++;
-
-  } else if (*line == '"') {
-    line++;
-    uns start_copy = copied;
-    while (1) {
-      byte *start = line;
-      uns escape = 0;
-      while (*line) {
-       if (*line == '"' && !escape)
-         break;
-       else if (*line == '\\')
-         escape ^= 1;
-       else
-         escape = 0;
-       line++;
-      }
-      append(start, line);
-      if (*line)
-       break;
-      if (!escape)
-       copy_buf.ptr[copied-1] = '\n';
-      else // merge two lines
-       copied -= 2;
-      if (!get_line())
-       return "Unterminated quoted word at the end";
-    }
-    line++;
-
-    byte *tmp = stk_str_unesc(copy_buf.ptr + start_copy);
-    uns l = strlen(tmp);
-    bb_grow(&copy_buf, start_copy + l + 1);
-    strcpy(copy_buf.ptr + start_copy, tmp);
-    copied = start_copy + l + 1;
-
-  } else {
-    // promised that *line is non-null and non-blank
-    byte *start = line;
-    while (*line && !Cblank(*line) && !CONTROL_CHAR(*line)
-       && (*line != '=' || !is_command_name))
-      line++;
-    if (*line == '=') {                                // nice for setting from a command-line
-      if (line == start)
-       return "Assignment without a variable";
-      *line = ' ';
-    }
-    if (line == start)                         // already the first char is control
-      line++;
-    append(start, line);
-  }
-  while (Cblank(*line))
-    line++;
-  return NULL;
-}
-
-static byte *
-get_token(uns is_command_name, byte **msg)
-{
-  *msg = NULL;
-  while (1) {
-    if (!*line || *line == '#') {
-      if (!is_command_name || !get_line())
-       return NULL;
-    } else if (*line == ';') {
-      *msg = get_word(0);
-      if (!is_command_name || *msg)
-       return NULL;
-    } else if (*line == '\\' && !line[1]) {
-      if (!get_line()) {
-       *msg = "Last line ends by a backslash";
-       return NULL;
-      }
-      if (!*line || *line == '#')
-       log(L_WARN, "The line %s:%d following a backslash is empty", name_parse_fb, line_num);
-    } else {
-      split_grow(&word_buf, words+1);
-      uns start = copied;
-      word_buf.ptr[words++] = copied;
-      *msg = get_word(is_command_name);
-      return *msg ? NULL : copy_buf.ptr + start;
-    }
-  }
-}
-
-static byte *
-split_command(void)
-{
-  words = copied = ends_by_brace = 0;
-  byte *msg, *start_word;
-  if (!(start_word = get_token(1, &msg)))
-    return msg;
-  if (*start_word == '{')                      // only one opening brace
-    return "Unexpected opening brace";
-  while (*line != '}')                         // stays for the next time
-  {
-    if (!(start_word = get_token(0, &msg)))
-      return msg;
-    if (*start_word == '{') {
-      words--;                                 // discard the brace
-      ends_by_brace = 1;
-      break;
-    }
-  }
-  return NULL;
-}
-
-/* Parsing multiple files */
-
-static byte *
-parse_fastbuf(byte *name_fb, struct fastbuf *fb, uns depth)
-{
-  byte *msg;
-  name_parse_fb = name_fb;
-  parse_fb = fb;
-  line_num = 0;
-  line = line_buf;
-  *line = 0;
-  while (1)
-  {
-    msg = split_command();
-    if (msg)
-      goto error;
-    if (!words)
-      return NULL;
-    byte *name = copy_buf.ptr + word_buf.ptr[0];
-    byte *pars[words-1];
-    for (uns i=1; i<words; i++)
-      pars[i-1] = copy_buf.ptr + word_buf.ptr[i];
-    if (!strcasecmp(name, "include"))
-    {
-      if (words != 2)
-       msg = "Expecting one filename";
-      else if (depth > 8)
-       msg = "Too many nested files";
-      else if (*line && *line != '#')          // because the contents of line_buf is not re-entrant and will be cleared
-       msg = "The input command must be the last one on a line";
-      if (msg)
-       goto error;
-      struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
-      if (!new_fb) {
-       msg = cf_printf("Cannot open file %s: %m", pars[0]);
-       goto error;
-      }
-      uns ll = line_num;
-      msg = parse_fastbuf(stk_strdup(pars[0]), new_fb, depth+1);
-      line_num = ll;
-      bclose(new_fb);
-      if (msg)
-       goto error;
-      parse_fb = fb;
-      continue;
-    }
-    enum cf_operation op;
-    byte *c = strchr(name, ':');
-    if (!c)
-      op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
-    else {
-      *c++ = 0;
-      switch (Clocase(*c)) {
-       case 's': op = OP_SET; break;
-       case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
-       case 'a': op = Clocase(c[1]) == 'p' ? OP_APPEND : OP_AFTER; break;
-       case 'p': op = OP_PREPEND; break;
-       case 'r': op = OP_REMOVE; break;
-       case 'e': op = OP_EDIT; break;
-       case 'b': op = OP_BEFORE; break;
-       default: op = OP_SET; break;
-      }
-      if (strcasecmp(c, op_names[op])) {
-       msg = cf_printf("Unknown operation %s", c);
-       goto error;
-      }
-    }
-    if (ends_by_brace)
-      op |= OP_OPEN;
-    msg = interpret_line(name, op, words-1, pars);
-    if (msg)
-      goto error;
-  }
-error:
-  log(L_ERROR, "File %s, line %d: %s", name_fb, line_num, msg);
-  return "included from here";
-}
-
-#ifndef DEFAULT_CONFIG
-#define DEFAULT_CONFIG NULL
-#endif
-byte *cf_def_file = DEFAULT_CONFIG;
-
-static int
-load_file(byte *file)
-{
-  init_stack();
-  struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
-  if (!fb) {
-    log(L_ERROR, "Cannot open %s: %m", file);
-    return 1;
-  }
-  byte *msg = parse_fastbuf(file, fb, 0);
-  bclose(fb);
-  int err = !!msg || done_stack();
-  if (!err)
-    cf_def_file = NULL;
-  return err;
-}
-
-static int
-load_string(byte *string)
-{
-  init_stack();
-  struct fastbuf fb;
-  fbbuf_init_read(&fb, string, strlen(string), 0);
-  byte *msg = parse_fastbuf("memory string", &fb, 0);
-  return !!msg || done_stack();
-}
-
-/* Command-line parser */
-
-static void
-load_default(void)
-{
-  if (cf_def_file)
-    if (cf_load(cf_def_file))
-      die("Cannot load default config %s", cf_def_file);
-}
-
-int
-cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
-{
-  static int other_options = 0;
-  while (1) {
-    int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
-    if (res == 'S' || res == 'C' || res == 0x64436667)
-    {
-      if (other_options)
-       die("The -S and -C options must precede all other arguments");
-      if (res == 'S') {
-       postpone_commit = 1;
-       load_default();
-       if (cf_set(optarg))
-         die("Cannot set %s", optarg);
-      } else if (res == 'C') {
-       postpone_commit = 1;
-       if (cf_load(optarg))
-         die("Cannot load config file %s", optarg);
-      }
-#ifdef CONFIG_DEBUG
-      else {   /* --dumpconfig */
-       load_default();
-       final_commit();
-       struct fastbuf *b = bfdopen(1, 4096);
-       cf_dump_sections(b);
-       bclose(b);
-       exit(0);
-      }
-#endif
-    } else {
-      /* unhandled option or end of options */
-      if (res != ':' && res != '?')
-       load_default();
-      final_commit();
-      other_options++;
-      return res;
-    }
-  }
-}
-
-/* Debug dumping */
-
-static void
-spaces(struct fastbuf *fb, uns nr)
-{
-  for (uns i=0; i<nr; i++)
-    bputs(fb, "  ");
-}
-
-static void
-dump_basic(struct fastbuf *fb, void *ptr, enum cf_type type, union cf_union *u)
-{
-  switch (type) {
-    case CT_INT:       bprintf(fb, "%d ", *(uns*)ptr); break;
-    case CT_U64:       bprintf(fb, "%llu ", *(u64*)ptr); break;
-    case CT_DOUBLE:    bprintf(fb, "%lg ", *(double*)ptr); break;
-    case CT_IP:                bprintf(fb, "%08x ", *(uns*)ptr); break;
-    case CT_STRING:
-      if (*(byte**)ptr)
-       bprintf(fb, "'%s' ", *(byte**)ptr);
-      else
-       bprintf(fb, "NULL ");
-      break;
-    case CT_LOOKUP:    bprintf(fb, "%s ", *(int*)ptr >= 0 ? u->lookup[ *(int*)ptr ] : (byte*) "???"); break;
-    case CT_USER:
-      if (u->utype->dumper)
-       u->utype->dumper(fb, ptr);
-      else
-       bprintf(fb, "??? ");
-      break;
-  }
-}
-
-static void dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr);
-
-static byte *class_names[] = { "end", "static", "dynamic", "parser", "section", "list" };
-static byte *type_names[] = { "int", "u64", "double", "ip", "string", "lookup", "user" };
-
-static void
-dump_item(struct fastbuf *fb, struct cf_item *item, int level, void *ptr)
-{
-  ptr += (addr_int_t) item->ptr;
-  enum cf_type type = item->type;
-  uns size = type_size(item->type, item->u.utype);
-  int i;
-  spaces(fb, level);
-  bprintf(fb, "%s: C%s #", item->name, class_names[item->cls]);
-  if (item->number == CF_ANY_NUM)
-    bputs(fb, "any ");
-  else
-    bprintf(fb, "%d ", item->number);
-  if (item->cls == CC_STATIC || item->cls == CC_DYNAMIC) {
-    bprintf(fb, "T%s ", type_names[type]);
-    if (item->type == CT_USER)
-      bprintf(fb, "U%s S%d ", item->u.utype->name, size);
-  }
-  if (item->cls == CC_STATIC) {
-    for (i=0; i<item->number; i++)
-      dump_basic(fb, ptr + i * size, type, &item->u);
-  } else if (item->cls == CC_DYNAMIC) {
-    ptr = * (void**) ptr;
-    if (ptr) {
-      int real_nr = * (int*) (ptr - size);
-      bprintf(fb, "N%d ", real_nr);
-      for (i=0; i<real_nr; i++)
-       dump_basic(fb, ptr + i * size, type, &item->u);
-    } else
-      bprintf(fb, "NULL ");
-  }
-  bputc(fb, '\n');
-  if (item->cls == CC_SECTION)
-    dump_section(fb, item->u.sec, level+1, ptr);
-  else if (item->cls == CC_LIST) {
-    uns idx = 0;
-    CLIST_FOR_EACH(cnode *, n, * (clist*) ptr) {
-      spaces(fb, level+1);
-      bprintf(fb, "item %d\n", ++idx);
-      dump_section(fb, item->u.sec, level+2, n);
-    }
-  }
-}
-
-static void
-dump_section(struct fastbuf *fb, struct cf_section *sec, int level, void *ptr)
-{
-  spaces(fb, level);
-  bprintf(fb, "S%d F%x:\n", sec->size, sec->flags);
-  for (struct cf_item *item=sec->cfg; item->cls; item++)
-    dump_item(fb, item, level, ptr);
-}
-
-void
-cf_dump_sections(struct fastbuf *fb)
-{
-  dump_section(fb, &sections, 0, NULL);
-}
-
-/* TODO
- * - more space efficient journal
- */
diff --git a/lib/conf2.t b/lib/conf2.t
deleted file mode 100644 (file)
index 1ba70ae..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-# test config file
-#include lib/conf2.t ; top.xa=1
-#include 'non-existent file'; #top.xa=1
-Top { \
-
-  nr1=16       #!!!
-  nrs1         2 3 5 \
-           7 11 13 \
-           \
-           17M
-  nrs2 3 3k 3 3 3 ; \
-  str1 "hello,\t\x2bworld%%\n"
-  str2 'Hagenuk,
-      the best' "\
-      " qu'est-ce que c'est?
-  u1   0xbadcafebadbeefc0
-  str2:prepend prepended
-  str2:append appended
-  d1 7%
-  d1   -1.14e-25
-  firsttime ; secondtime 56
-  ^top.master:set      alice HB8+
-  slaves:clear
-  ip 0xa
-  ip 195.113.31.123
-  look Alpha
-  look:prepend Beta GAMMA
-  numbers 11000 65535
-};;;;;;
-
-unknown.ignored :-)
-
-top.slaves     cairns gpua 7 7 -10% +10%
-top.slaves     daintree rafc 4 5 -171%
-top.slaves     coogee pum 9 8
-top.slaves:prepend     {name=bondi; level=\
-  "PUG"; confidence    10 10}
-top.slaves:remove {name daintree}
-top.slaveS:edit {level PUG} Bondi PUG!
-top.slaveS:before {level pum}{
-  confidence 2
-  list 123 456 789
-}
-top.slaves:copy {name coogee} Coogee2 PUM
-
-topp.a=15
-top.nr1=   ' 15'
-a { ;-D }