]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Implements wildcard matching in config file include.
authorOndrej Zajicek <santiago@crfreenet.org>
Wed, 18 Jul 2012 17:29:33 +0000 (19:29 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Wed, 18 Jul 2012 17:29:33 +0000 (19:29 +0200)
Also fixes some minor bugs in include.

Thanks Kelly Cochran for suggestion and draft patch.

conf/cf-lex.l
conf/conf.c
conf/conf.h
filter/f-util.c
filter/test.conf.inc
nest/cli.c
sysdep/unix/main.c

index 8cd52c421fec854b6940f7862bc12c3eb677dc64..c8eae0e87652167d2ce09574f072a99ceafc5473 100644 (file)
 #include <stdlib.h>
 #include <stdarg.h>
 #include <unistd.h>
+#include <libgen.h>
+#include <glob.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #define PARSER 1
 
@@ -64,27 +70,23 @@ struct sym_scope {
 };
 static struct sym_scope *conf_this_scope;
 
-#define MAX_INCLUDE_DEPTH 5
-
-static struct include_file_stack *ifs_head;
-static int ifs_depth;
-
 static int cf_hash(byte *c);
 static struct symbol *cf_find_sym(byte *c, unsigned int h0);
 
 linpool *cfg_mem;
 
 int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
-int (*cf_open_hook)(char *filename);
 struct include_file_stack *ifs;
+static struct include_file_stack *ifs_head;
+
+#define MAX_INCLUDE_DEPTH 8
 
-#define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->conf_fd);
+#define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->fd);
 #define YY_NO_UNPUT
 #define YY_FATAL_ERROR(msg) cf_error(msg)
 
-static void new_include(void);
+static void cf_include(char *arg, int alen);
 static int check_eof(void);
-static struct include_file_stack *new_stack(struct include_file_stack *old);
 
 %}
 
@@ -103,7 +105,23 @@ WHITE [ \t]
 include   ^{WHITE}*include{WHITE}*\".*\"{WHITE}*;
 
 %%
-{include} { if(cf_open_hook) new_include(); }
+{include} {
+  char *start, *end;
+
+  if (!ifs->depth)
+    cf_error("Include not allowed in CLI");
+
+  start = strchr(yytext, '"');
+  start++;
+
+  end = strchr(start, '"');
+  *end = 0;
+
+  if (start == end)
+    cf_error("Include with empty argument");
+
+  cf_include(start, end-start);
+}
 
 {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ {
 #ifdef IPV6
@@ -200,11 +218,11 @@ else: {
 
 ["][^"\n]*\n   cf_error("Unterminated string");
 
-<INITIAL,COMMENT><<EOF>>       { if(check_eof()) return END; }
+<INITIAL,COMMENT><<EOF>>       { if (check_eof()) return END; }
 
 {WHITE}+
 
-\n     ifs->conf_lino++;
+\n     ifs->lino++;
 
 #      BEGIN(COMMENT);
 
@@ -213,14 +231,14 @@ else: {
 .      cf_error("Unknown character");
 
 <COMMENT>\n {
-  ifs->conf_lino++;
+  ifs->lino++;
   BEGIN(INITIAL);
 }
 
 <COMMENT>.
 
 <CCOMM>\*\/    BEGIN(INITIAL);
-<CCOMM>\n      ifs->conf_lino++;
+<CCOMM>\n      ifs->lino++;
 <CCOMM>\/\*    cf_error("Comment nesting not supported");
 <CCOMM><<EOF>> cf_error("Unterminated comment");
 <CCOMM>.
@@ -246,48 +264,141 @@ cf_hash(byte *c)
   return h;
 }
 
-/* Open included file with properly swapped buffers */
+
+/*
+ * IFS stack - it contains structures needed for recursive processing
+ * of include in config files. On the top of the stack is a structure
+ * for currently processed file. Other structures are either for
+ * active files interrupted because of include directive (these have
+ * fd and flex buffer) or for inactive files scheduled to be processed
+ * later (when parent requested including of several files by wildcard
+ * match - these do not have fd and flex buffer yet).
+ *
+ * FIXME: Most of these ifs and include functions are really sysdep/unix.
+ *
+ * FIXME: Resources (fd, flex buffers and glob data) in IFS stack
+ * are not freed when cf_error() is called.
+ */
+
+static struct include_file_stack *
+push_ifs(struct include_file_stack *old)
+{
+  struct include_file_stack *ret;
+  ret = cfg_allocz(sizeof(struct include_file_stack));
+  ret->lino = 1;
+  ret->prev = old;
+  return ret;
+}
+
+static struct include_file_stack *
+pop_ifs(struct include_file_stack *old)
+{
+ yy_delete_buffer(old->buffer);
+ close(old->fd);
+ return old->prev;
+}
+
 static void
-new_include(void)
+enter_ifs(struct include_file_stack *new)
 {
-  char *fname, *p = NULL;
+  if (!new->buffer)
+    {
+      new->fd = open(new->file_name, O_RDONLY);
+      if (new->fd < 0)
+        {
+          ifs = ifs->up;
+         cf_error("Unable to open included file %s: %m", new->file_name);
+        }
+
+      new->buffer = yy_create_buffer(NULL, YY_BUF_SIZE);
+    }
 
-  if ((fname = strchr(yytext, '"')) != NULL) {
+  yy_switch_to_buffer(new->buffer);
+}
 
-    if ((p = strchr(++fname, '"')) != NULL) *p = '\0';
+static void
+cf_include(char *arg, int alen)
+{
+  struct include_file_stack *base_ifs = ifs;
+  int new_depth, rv, i;
+  char *patt;
+  glob_t g;
+
+  new_depth = ifs->depth + 1;
+  if (new_depth > MAX_INCLUDE_DEPTH)
+    cf_error("Max include depth reached");
 
-    if (ifs_depth >= MAX_INCLUDE_DEPTH)
-      cf_error("Max include depth reached.");
+  /* expand arg to properly handle relative filenames */
+  if (*arg != '/')
+    {
+      int dlen = strlen(ifs->file_name);
+      char *dir = alloca(dlen + 1);
+      patt = alloca(dlen + alen + 2);
+      memcpy(dir, ifs->file_name, dlen + 1);
+      sprintf(patt, "%s/%s", dirname(dir), arg);
+    }
+  else
+    patt = arg;
 
-    /* Save current stack */
-    ifs->stack = YY_CURRENT_BUFFER;
-    /* Prepare new stack */
-    ifs->next = new_stack(ifs);
-    ifs = ifs->next;
-    strcpy(ifs->conf_fname, fname); /* XXX: strlcpy should be here */
-    ifs->conf_fd = cf_open_hook(fname);
+  /* Skip globbing if there are no wildcards, mainly to get proper
+     response when the included config file is missing */
+  if (!strpbrk(arg, "?*["))
+    {
+      ifs = push_ifs(ifs);
+      ifs->file_name = cfg_strdup(patt);
+      ifs->depth = new_depth;
+      ifs->up = base_ifs;
+      enter_ifs(ifs);
+      return;
+    }
 
-    yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
-  }
+  /* Expand the pattern */
+  rv = glob(patt, GLOB_ERR | GLOB_NOESCAPE, NULL, &g);
+  if (rv == GLOB_ABORTED)
+    cf_error("Unable to match pattern %s: %m", patt);
+  if ((rv != 0) || (g.gl_pathc <= 0))
+    return;
+
+  /*
+   * Now we put all found files to ifs stack in reverse order, they
+   * will be activated and processed in order as ifs stack is popped
+   * by pop_ifs() and enter_ifs() in check_eof().
+   */
+  for(i = g.gl_pathc - 1; i >= 0; i--)
+    {
+      char *fname = g.gl_pathv[i];
+      struct stat fs;
+
+      if (stat(fname, &fs) < 0)
+        cf_error("Unable to stat included file %s: %m", fname);
+
+      if (fs.st_mode & S_IFDIR)
+        continue;
+
+      /* Prepare new stack item */
+      ifs = push_ifs(ifs);
+      ifs->file_name = cfg_strdup(fname);
+      ifs->depth = new_depth;
+      ifs->up = base_ifs;
+    }
+
+  globfree(&g);
+  enter_ifs(ifs);
 }
 
 static int
 check_eof(void)
 {
-       if (ifs == ifs_head) {
-               /* EOF in main config file */
-               ifs->conf_lino = 1;
-               return 1;
-       }
+  if (ifs == ifs_head)
+    {
+      /* EOF in main config file */
+      ifs->lino = 1; /* Why this? */
+      return 1;
+    }
 
-       ifs_depth--;
-       close(ifs->conf_fd);
-       ifs = ifs->prev;
-       ifs->next = NULL;
-       
-       yy_delete_buffer(YY_CURRENT_BUFFER);
-       yy_switch_to_buffer(ifs->stack);
-       return 0;
+  ifs = pop_ifs(ifs);
+  enter_ifs(ifs);
+  return 0;
 }
 
 static struct symbol *
@@ -415,16 +526,6 @@ cf_lex_init_kh(void)
   kw_hash_inited = 1;
 }
 
-static struct include_file_stack *
-new_stack(struct include_file_stack *old)
-{
-  struct include_file_stack *ret;
-  ret = cfg_allocz(sizeof(struct include_file_stack));
-  ret->conf_lino = 1;
-  ret->prev = old;
-  return ret;
-}
-
 /**
  * cf_lex_init - initialize the lexer
  * @is_cli: true if we're going to parse CLI command, false for configuration
@@ -437,19 +538,23 @@ cf_lex_init(int is_cli, struct config *c)
 {
   if (!kw_hash_inited)
     cf_lex_init_kh();
-  ifs_head = new_stack(NULL);
-  ifs = ifs_head;
-  ifs_depth = 0;
-  if (!is_cli) {
-    ifs->conf_fd = c->file_fd;
-    ifs_depth = 1;
-    strcpy(ifs->conf_fname, c->file_name);
-  }
+
+  ifs_head = ifs = push_ifs(NULL);
+  if (!is_cli) 
+    {
+      ifs->file_name = c->file_name;
+      ifs->fd = c->file_fd;
+      ifs->depth = 1;
+    }
+
   yyrestart(NULL);
+  ifs->buffer = YY_CURRENT_BUFFER;
+
   if (is_cli)
     BEGIN(CLI);
   else
     BEGIN(INITIAL);
+
   conf_this_scope = cfg_allocz(sizeof(struct sym_scope));
   conf_this_scope->active = 1;
 }
index 13049be4512a1ac465cf66a0c56d4665596b3c11..9375861fbf741ce8bffbb4a1511c44ca875ea914 100644 (file)
@@ -357,8 +357,8 @@ cf_error(char *msg, ...)
   if (bvsnprintf(buf, sizeof(buf), msg, args) < 0)
     strcpy(buf, "<bug: error message too long>");
   new_config->err_msg = cfg_strdup(buf);
-  new_config->err_lino = ifs->conf_lino;
-  new_config->err_file_name = ifs->conf_fname;
+  new_config->err_lino = ifs->lino;
+  new_config->err_file_name = ifs->file_name;
   longjmp(conf_jmpbuf, 1);
 }
 
index b4ec315707c4462d5d261c465f172245f6a10363..c76832b607c85f1c5cbb95c9ef565f5aa46f9c41 100644 (file)
@@ -12,7 +12,6 @@
 #include "lib/resource.h"
 #include "lib/timer.h"
 
-#define BIRD_FNAME_MAX 255     /* Would be better to use some UNIX define */
 
 /* Configuration structure */
 
@@ -91,7 +90,6 @@ void cfg_copy_list(list *dest, list *src, unsigned node_size);
 /* Lexer */
 
 extern int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
-extern int (*cf_open_hook)(char *filename);
 
 struct symbol {
   struct symbol *next;
@@ -117,12 +115,14 @@ struct symbol {
 #define SYM_VARIABLE 0x100     /* 0x100-0x1ff are variable types */
 
 struct include_file_stack {
-        void   *stack; /* Internal lexer state */
-        unsigned int    conf_lino; /* Current file lineno (at include) */
-        char            conf_fname[BIRD_FNAME_MAX]; /* Current file name */
-        int             conf_fd; /* Current file descriptor */
-        struct include_file_stack *prev;
-        struct include_file_stack *next;
+  void *buffer;                                /* Internal lexer state */
+  char *file_name;                     /* File name */
+  int fd;                              /* File descriptor */
+  int lino;                            /* Current line num */
+  int depth;                           /* Include depth, 0 = cannot include */
+
+  struct include_file_stack *prev;     /* Previous record in stack */
+  struct include_file_stack *up;       /* Parent (who included this file) */
 };
 
 extern struct include_file_stack *ifs;
index 5908ac644791b02061eaf1a71b4908effb7cb6ae..def2b2482dac4bbf91b16821acfa28af472164e0 100644 (file)
@@ -19,7 +19,7 @@ f_new_inst(void)
   ret = cfg_alloc(sizeof(struct f_inst));
   ret->code = ret->aux = 0;
   ret->arg1 = ret->arg2 = ret->next = NULL;
-  ret->lineno = ifs->conf_lino;
+  ret->lineno = ifs->lino;
   return ret;
 }
 
@@ -60,7 +60,7 @@ f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *a
 {
   struct f_inst_roa_check *ret = cfg_allocz(sizeof(struct f_inst_roa_check));
   ret->i.code = P('R','C');
-  ret->i.lineno = ifs->conf_lino;
+  ret->i.lineno = ifs->lino;
   ret->i.arg1 = prefix;
   ret->i.arg2 = asn;
   /* prefix == NULL <-> asn == NULL */
index 10fc70142edcf6432ac0e9f586e0b24538930ea7..109a49c53262dcea6479de55561b071f10732def 100644 (file)
@@ -3,4 +3,3 @@ print "Entering include";
 print "Should be 2:  ", 1+1;
 print "Leaving include";
 
-
index f9cc0f47e5cf389e82fddfbccca83e1d6413c716..d245790b01d09aaea66290f2941614c4db300a6a 100644 (file)
@@ -251,7 +251,6 @@ cli_command(struct cli *c)
   bzero(&f, sizeof(f));
   f.mem = c->parser_pool;
   cf_read_hook = cli_cmd_read_hook;
-  cf_open_hook = NULL;
   cli_rh_pos = c->rx_buf;
   cli_rh_len = strlen(c->rx_buf);
   cli_rh_trick_flag = 0;
index e0563aaedd5a3d41de5f331a31fb228fbaa311c5..f0344a8f4136d75bdb0701dcd6097af35f856680 100644 (file)
@@ -162,29 +162,6 @@ cf_read(byte *dest, unsigned int len, int fd)
   return l;
 }
 
-static int
-cf_open(char *filename)
-{
-  char full_name[BIRD_FNAME_MAX];
-  char *cur = filename;
-  int ret;
-
-  if (*filename != '/') {
-    char dir[BIRD_FNAME_MAX];
-    strncpy(dir, config_name, sizeof(dir));
-    dir[sizeof(dir)-1] = 0;
-    snprintf(full_name, sizeof(full_name), "%s/%s", dirname(dir), filename);
-    full_name[sizeof(full_name)-1] = 0;
-    cur = full_name;
-  }
-
-  if ((ret = open(cur, O_RDONLY)) == -1)
-    cf_error("Unable to open included configuration file: %s", cur);
-
-  return ret;
-}
-
-
 void
 sysdep_preconfig(struct config *c)
 {
@@ -216,7 +193,6 @@ unix_read_config(struct config **cp, char *name)
   if (conf->file_fd < 0)
     return 0;
   cf_read_hook = cf_read;
-  cf_open_hook = cf_open;
   ret = config_parse(conf);
   close(conf->file_fd);
   return ret;