]> mj.ucw.cz Git - libucw.git/blobdiff - ucw/conf-input.c
Merge remote-tracking branch 'origin/dev-table' into dev-table
[libucw.git] / ucw / conf-input.c
index fffc6d57e5cd5d8088755bb9c0730768c63cc421..f8d2eb840ce21eb693df10e68fdb0eb9151e4332 100644 (file)
  *     UCW Library -- Configuration files: parsing input streams
  *
  *     (c) 2001--2006 Robert Spalek <robert@ucw.cz>
- *     (c) 2003--2009 Martin Mares <mj@ucw.cz>
+ *     (c) 2003--2012 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 "ucw/lib.h"
-#include "ucw/conf.h"
-#include "ucw/getopt.h"
-#include "ucw/conf-internal.h"
-#include "ucw/clists.h"
-#include "ucw/mempool.h"
-#include "ucw/fastbuf.h"
-#include "ucw/chartype.h"
-#include "ucw/string.h"
-#include "ucw/stkstring.h"
+#include <ucw/lib.h>
+#include <ucw/conf.h>
+#include <ucw/conf-internal.h>
+#include <ucw/clists.h>
+#include <ucw/mempool.h>
+#include <ucw/fastbuf.h>
+#include <ucw/chartype.h>
+#include <ucw/string.h>
+#include <ucw/stkstring.h>
 
 #include <stdlib.h>
 #include <string.h>
+#include <errno.h>
 #include <fcntl.h>
 
 /* Text file parser */
 
-static const char *name_parse_fb;
-static struct fastbuf *parse_fb;
-static uns line_num;
-
 #define MAX_LINE       4096
-static char line_buf[MAX_LINE];
-static char *line = line_buf;
 
-#include "ucw/bbuf.h"
-static bb_t copy_buf;
-static uns copied;
+#include <ucw/bbuf.h>
 
-#define GBUF_TYPE      uns
+#define GBUF_TYPE      uint
 #define GBUF_PREFIX(x) split_##x
-#include "ucw/gbuf.h"
-static split_t word_buf;
-static uns words;
-static uns ends_by_brace;              // the line is ended by "{"
+#include <ucw/gbuf.h>
+
+struct cf_parser_state {
+  const char *name_parse_fb;
+  struct fastbuf *parse_fb;
+  uint line_num;
+  char *line;
+  split_t word_buf;
+  uint words;
+  uint ends_by_brace;          // the line is ended by "{"
+  bb_t copy_buf;
+  uint copied;
+  char line_buf[];
+};
 
 static int
-get_line(char **msg)
+get_line(struct cf_parser_state *p, char **msg)
 {
-  int err = bgets_nodie(parse_fb, line_buf, MAX_LINE);
-  line_num++;
+  int err = bgets_nodie(p->parse_fb, p->line_buf, MAX_LINE);
+  p->line_num++;
   if (err <= 0) {
     *msg = err < 0 ? "Line too long" : NULL;
     return 0;
   }
-  line = line_buf;
-  while (Cblank(*line))
-    line++;
+  p->line = p->line_buf;
+  while (Cblank(*p->line))
+    p->line++;
   return 1;
 }
 
 static void
-append(char *start, char *end)
+append(struct cf_parser_state *p, char *start, char *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;
+  uint len = end - start;
+  bb_grow(&p->copy_buf, p->copied + len + 1);
+  memcpy(p->copy_buf.ptr + p->copied, start, len);
+  p->copied += len + 1;
+  p->copy_buf.ptr[p->copied-1] = 0;
 }
 
 static char *
-get_word(uns is_command_name)
+get_word(struct cf_parser_state *p, uint is_command_name)
 {
   char *msg;
+  char *line = p->line;
+
   if (*line == '\'') {
     line++;
     while (1) {
       char *start = line;
       while (*line && *line != '\'')
        line++;
-      append(start, line);
+      append(p, start, line);
       if (*line)
        break;
-      copy_buf.ptr[copied-1] = '\n';
-      if (!get_line(&msg))
+      p->copy_buf.ptr[p->copied-1] = '\n';
+      if (!get_line(p, &msg))
        return msg ? : "Unterminated apostrophe word at the end";
+      line = p->line;
     }
     line++;
 
   } else if (*line == '"') {
     line++;
-    uns start_copy = copied;
+    uint start_copy = p->copied;
     while (1) {
       char *start = line;
-      uns escape = 0;
+      uint escape = 0;
       while (*line) {
        if (*line == '"' && !escape)
          break;
@@ -103,23 +108,24 @@ get_word(uns is_command_name)
          escape = 0;
        line++;
       }
-      append(start, line);
+      append(p, start, line);
       if (*line)
        break;
       if (!escape)
-       copy_buf.ptr[copied-1] = '\n';
+       p->copy_buf.ptr[p->copied-1] = '\n';
       else // merge two lines
-       copied -= 2;
-      if (!get_line(&msg))
+       p->copied -= 2;
+      if (!get_line(p, &msg))
        return msg ? : "Unterminated quoted word at the end";
+      line = p->line;
     }
     line++;
 
-    char *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;
+    char *tmp = stk_str_unesc(p->copy_buf.ptr + start_copy);
+    uint l = strlen(tmp);
+    bb_grow(&p->copy_buf, start_copy + l + 1);
+    strcpy(p->copy_buf.ptr + start_copy, tmp);
+    p->copied = start_copy + l + 1;
 
   } else {
     // promised that *line is non-null and non-blank
@@ -135,59 +141,60 @@ get_word(uns is_command_name)
     }
     if (line == start)                         // already the first char is control
       line++;
-    append(start, line);
+    append(p, start, line);
   }
   while (Cblank(*line))
     line++;
+  p->line = line;
   return NULL;
 }
 
 static char *
-get_token(uns is_command_name, char **err)
+get_token(struct cf_parser_state *p, uint is_command_name, char **err)
 {
   *err = NULL;
   while (1) {
-    if (!*line || *line == '#') {
-      if (!is_command_name || !get_line(err))
+    if (!*p->line || *p->line == '#') {
+      if (!is_command_name || !get_line(p, err))
        return NULL;
-    } else if (*line == ';') {
-      *err = get_word(0);
+    } else if (*p->line == ';') {
+      *err = get_word(p, 0);
       if (!is_command_name || *err)
        return NULL;
-    } else if (*line == '\\' && !line[1]) {
-      if (!get_line(err)) {
+    } else if (*p->line == '\\' && !p->line[1]) {
+      if (!get_line(p, err)) {
        if (!*err)
          *err = "Last line ends by a backslash";
        return NULL;
       }
-      if (!*line || *line == '#')
-       msg(L_WARN, "The line %s:%d following a backslash is empty", name_parse_fb ? : "", line_num);
+      if (!*p->line || *p->line == '#')
+       msg(L_WARN, "The line %s:%d following a backslash is empty", p->name_parse_fb ? : "", p->line_num);
     } else {
-      split_grow(&word_buf, words+1);
-      uns start = copied;
-      word_buf.ptr[words++] = copied;
-      *err = get_word(is_command_name);
-      return *err ? NULL : copy_buf.ptr + start;
+      split_grow(&p->word_buf, p->words+1);
+      uint start = p->copied;
+      p->word_buf.ptr[p->words++] = p->copied;
+      *err = get_word(p, is_command_name);
+      return *err ? NULL : p->copy_buf.ptr + start;
     }
   }
 }
 
 static char *
-split_command(void)
+split_command(struct cf_parser_state *p)
 {
-  words = copied = ends_by_brace = 0;
+  p->words = p->copied = p->ends_by_brace = 0;
   char *msg, *start_word;
-  if (!(start_word = get_token(1, &msg)))
+  if (!(start_word = get_token(p, 1, &msg)))
     return msg;
   if (*start_word == '{')                      // only one opening brace
     return "Unexpected opening brace";
-  while (*line != '}')                         // stays for the next time
+  while (*p->line != '}')                      // stays for the next time
   {
-    if (!(start_word = get_token(0, &msg)))
+    if (!(start_word = get_token(p, 0, &msg)))
       return msg;
     if (*start_word == '{') {
-      words--;                                 // discard the brace
-      ends_by_brace = 1;
+      p->words--;                              // discard the brace
+      p->ends_by_brace = 1;
       break;
     }
   }
@@ -196,48 +203,68 @@ split_command(void)
 
 /* Parsing multiple files */
 
+static int
+maybe_commit(struct cf_context *cc)
+{
+  if (cf_commit_all(cc->postpone_commit ? CF_NO_COMMIT : cc->everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
+    return 1;
+  if (!cc->postpone_commit)
+    cc->everything_committed = 1;
+  return 0;
+}
+
 static char *
-parse_fastbuf(const char *name_fb, struct fastbuf *fb, uns depth)
+parse_fastbuf(struct cf_context *cc, const char *name_fb, struct fastbuf *fb, uint depth)
 {
-  char *err;
-  name_parse_fb = name_fb;
-  parse_fb = fb;
-  line_num = 0;
-  line = line_buf;
-  *line = 0;
+  struct cf_parser_state *p = cc->parser;
+  if (!p)
+    p = cc->parser = xmalloc_zero(sizeof(*p) + MAX_LINE);
+  p->name_parse_fb = name_fb;
+  p->parse_fb = fb;
+  p->line_num = 0;
+  p->line = p->line_buf;
+  *p->line = 0;
+
+  if (!depth)
+    cf_init_stack(cc);
+
+  char *err = NULL;
   while (1)
   {
-    err = split_command();
+    err = split_command(p);
     if (err)
       goto error;
-    if (!words)
-      return NULL;
-    char *name = copy_buf.ptr + word_buf.ptr[0];
-    char *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 (!p->words)
+      break;
+    char *name = p->copy_buf.ptr + p->word_buf.ptr[0];
+    char *pars[p->words-1];
+    for (uint i=1; i<p->words; i++)
+      pars[i-1] = p->copy_buf.ptr + p->word_buf.ptr[i];
+    int optional_include = !strcasecmp(name, "optionalinclude");
+    if (optional_include || !strcasecmp(name, "include"))
     {
-      if (words != 2)
+      if (p->words != 2)
        err = "Expecting one filename";
       else if (depth > 8)
        err = "Too many nested files";
-      else if (*line && *line != '#')          // because the contents of line_buf is not re-entrant and will be cleared
+      else if (*p->line && *p->line != '#')    // because the contents of line_buf is not re-entrant and will be cleared
        err = "The include command must be the last one on a line";
       if (err)
        goto error;
       struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
       if (!new_fb) {
+       if (optional_include && errno == ENOENT)
+         continue;
        err = cf_printf("Cannot open file %s: %m", pars[0]);
        goto error;
       }
-      uns ll = line_num;
-      err = parse_fastbuf(stk_strdup(pars[0]), new_fb, depth+1);
-      line_num = ll;
+      uint ll = p->line_num;
+      err = parse_fastbuf(cc, stk_strdup(pars[0]), new_fb, depth+1);
+      p->line_num = ll;
       bclose(new_fb);
       if (err)
        goto error;
-      parse_fb = fb;
+      p->parse_fb = fb;
       continue;
     }
     enum cf_operation op;
@@ -265,70 +292,53 @@ parse_fastbuf(const char *name_fb, struct fastbuf *fb, uns depth)
        goto error;
       }
     }
-    if (ends_by_brace)
+    if (p->ends_by_brace)
       op |= OP_OPEN;
-    err = cf_interpret_line(name, op, words-1, pars);
+    err = cf_interpret_line(cc, name, op, p->words-1, pars);
     if (err)
       goto error;
   }
+
+  if (!depth)
+    {
+      if (cf_done_stack(cc))
+       err = "Unterminated block";
+      else if (maybe_commit(cc))
+       err = "Commit failed";
+    }
+  if (!err)
+    return NULL;
+
 error:
   if (name_fb)
-    msg(L_ERROR, "File %s, line %d: %s", name_fb, line_num, err);
-  else if (line_num == 1)
+    msg(L_ERROR, "File %s, line %d: %s", name_fb, p->line_num, err);
+  else if (p->line_num == 1)
     msg(L_ERROR, "Manual setting of configuration: %s", err);
   else
-    msg(L_ERROR, "Manual setting of configuration, line %d: %s", line_num, err);
+    msg(L_ERROR, "Manual setting of configuration, line %d: %s", p->line_num, err);
   return "included from here";
 }
 
-#ifndef DEFAULT_CONFIG
-#define DEFAULT_CONFIG NULL
-#endif
-char *cf_def_file = DEFAULT_CONFIG;
-static int cf_def_loaded;
-
-#ifndef ENV_VAR_CONFIG
-#define ENV_VAR_CONFIG NULL
-#endif
-char *cf_env_file = ENV_VAR_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(const char *file)
+load_file(struct cf_context *cc, const char *file)
 {
-  cf_init_stack();
   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
   if (!fb) {
-    msg(L_ERROR, "Cannot open %s: %m", file);
+    msg(L_ERROR, "Cannot open configuration file %s: %m", file);
     return 1;
   }
-  char *err_msg = parse_fastbuf(file, fb, 0);
+  char *err_msg = parse_fastbuf(cc, file, fb, 0);
   bclose(fb);
-  return !!err_msg || done_stack();
+  return !!err_msg;
 }
 
 static int
-load_string(const char *string)
+load_string(struct cf_context *cc, const char *string)
 {
-  cf_init_stack();
   struct fastbuf fb;
   fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
-  char *msg = parse_fastbuf(NULL, &fb, 0);
-  return !!msg || done_stack();
+  char *msg = parse_fastbuf(cc, NULL, &fb, 0);
+  return !!msg;
 }
 
 /* Safe loading and reloading */
@@ -342,61 +352,55 @@ struct conf_entry {       /* We remember a list of actions to apply upon reload */
   char *arg;
 };
 
-static clist conf_entries;
-
 static void
-cf_remember_entry(uns type, const char *arg)
+cf_remember_entry(struct cf_context *cc, uint type, const char *arg)
 {
-  if (!cf_need_journal)
-    return;
-  if (!postpone_commit)
+  if (!cc->enable_journal)
     return;
   struct conf_entry *ce = cf_malloc(sizeof(*ce));
   ce->type = type;
   ce->arg = cf_strdup(arg);
-  clist_add_tail(&conf_entries, &ce->n);
+  clist_add_tail(&cc->conf_entries, &ce->n);
 }
 
 int
 cf_reload(const char *file)
 {
+  struct cf_context *cc = cf_get_context();
+  ASSERT(cc->enable_journal);
   cf_journal_swap();
   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
-  uns ec = everything_committed;
-  everything_committed = 0;
+  uint ec = cc->everything_committed;
+  cc->everything_committed = 0;
 
-  if (!conf_entries.head.next)
-    clist_init(&conf_entries);
   clist old_entries;
-  clist_move(&old_entries, &conf_entries);
-  postpone_commit = 1;
+  clist_move(&old_entries, &cc->conf_entries);
+  cf_open_group();
 
   int err = 0;
   if (file)
-    err = load_file(file);
+    err = load_file(cc, file);
   else
     CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
       if (ce->type == CE_FILE)
-       err |= load_file(ce->arg);
+       err |= load_file(cc, ce->arg);
       else
-       err |= load_string(ce->arg);
+       err |= load_string(cc, ce->arg);
       if (err)
        break;
-      cf_remember_entry(ce->type, ce->arg);
+      cf_remember_entry(cc, ce->type, ce->arg);
     }
 
-  postpone_commit = 0;
-  if (!err)
-    err |= done_stack();
+  err |= cf_close_group();
 
   if (!err) {
     cf_journal_delete();
     cf_journal_commit_transaction(1, NULL);
   } else {
-    everything_committed = ec;
+    cc->everything_committed = ec;
     cf_journal_rollback_transaction(1, oldj);
     cf_journal_swap();
-    clist_move(&conf_entries, &old_entries);
+    clist_move(&cc->conf_entries, &old_entries);
   }
   return err;
 }
@@ -404,12 +408,13 @@ cf_reload(const char *file)
 int
 cf_load(const char *file)
 {
+  struct cf_context *cc = cf_get_context();
   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
-  int err = load_file(file);
+  int err = load_file(cc, file);
   if (!err) {
     cf_journal_commit_transaction(1, oldj);
-    cf_remember_entry(CE_FILE, file);
-    cf_def_loaded = 1;
+    cf_remember_entry(cc, CE_FILE, file);
+    cc->config_loaded = 1;
   } else
     cf_journal_rollback_transaction(1, oldj);
   return err;
@@ -418,93 +423,38 @@ cf_load(const char *file)
 int
 cf_set(const char *string)
 {
+  struct cf_context *cc = cf_get_context();
   struct cf_journal_item *oldj = cf_journal_new_transaction(0);
-  int err = load_string(string);
+  int err = load_string(cc, string);
   if (!err) {
     cf_journal_commit_transaction(0, oldj);
-    cf_remember_entry(CE_STRING, string);
+    cf_remember_entry(cc, CE_STRING, string);
   } else
     cf_journal_rollback_transaction(0, oldj);
   return err;
 }
 
-/* Command-line parser */
-
-static void
-load_default(void)
+void
+cf_revert(void)
 {
-  if (cf_def_loaded++)
-    return;
-  if (cf_def_file)
-    {
-      char *env;
-      if (cf_env_file && (env = getenv(cf_env_file)))
-        {
-         if (cf_load(env))
-           die("Cannot load config file %s", env);
-       }
-      else if (cf_load(cf_def_file))
-        die("Cannot load default config %s", cf_def_file);
-    }
-  else
-    {
-      // We need to create an empty pool and initialize all configuration items
-      struct cf_journal_item *oldj = cf_journal_new_transaction(1);
-      cf_init_stack();
-      done_stack();
-      cf_journal_commit_transaction(1, oldj);
-    }
+  cf_journal_swap();
+  cf_journal_delete();
 }
 
-static void
-final_commit(void)
+void
+cf_open_group(void)
 {
-  if (postpone_commit) {
-    postpone_commit = 0;
-    if (done_stack())
-      die("Cannot commit after the initialization");
-  }
+  struct cf_context *cc = cf_get_context();
+  cc->postpone_commit++;
 }
 
 int
-cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
+cf_close_group(void)
 {
-  clist_init(&conf_entries);
-  postpone_commit = 1;
-
-  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') {
-       load_default();
-       if (cf_set(optarg))
-         die("Cannot set %s", optarg);
-      } else if (res == 'C') {
-       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;
-    }
-  }
+  struct cf_context *cc = cf_get_context();
+  ASSERT(cc->postpone_commit);
+  if (!--cc->postpone_commit)
+    return maybe_commit(cc);
+  else
+    return 0;
 }