From 48ec367aabaaa5328f4072d237001e245a7363df Mon Sep 17 00:00:00 2001 From: Ondrej Filip Date: Sun, 11 Sep 2011 21:21:47 +0200 Subject: [PATCH] Configuration can include other files. --- conf/cf-lex.l | 89 ++++++++++++++++++++++++++++++++++++++----- conf/conf.c | 7 ++-- conf/conf.h | 23 +++++++++-- doc/bird.conf.example | 2 + doc/bird.sgml | 3 ++ filter/f-util.c | 2 +- filter/test.conf | 3 ++ filter/test.conf.inc | 6 +++ nest/cli.c | 1 + sysdep/unix/main.c | 34 +++++++++++++---- 10 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 filter/test.conf.inc diff --git a/conf/cf-lex.l b/conf/cf-lex.l index a5f70fff..79dbab29 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -63,19 +63,27 @@ struct sym_scope { }; static struct sym_scope *conf_this_scope; -int conf_lino; +#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 (*cf_read_hook)(byte *buf, unsigned int max, int fd); +int (*cf_open_hook)(char *filename); -#define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max); +#define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->conf_fd); #define YY_NO_UNPUT #define YY_FATAL_ERROR(msg) cf_error(msg) +static void new_include(void); +static int check_eof(void); +static struct include_file_stack *new_stack(struct include_file_stack *old); + %} %option noyywrap @@ -90,8 +98,10 @@ DIGIT [0-9] XIGIT [0-9a-fA-F] ALNUM [a-zA-Z_0-9] WHITE [ \t] +include ^{WHITE}*include{WHITE}*\".*\"{WHITE}*; %% +{include} { if(cf_open_hook) new_include(); } {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ { #ifdef IPV6 @@ -188,11 +198,11 @@ else: { ["][^"\n]*\n cf_error("Unterminated string"); -<> return END; +<> { if(check_eof()) return END; } {WHITE}+ -\n conf_lino++; +\n ifs->conf_lino++; # BEGIN(COMMENT); @@ -201,14 +211,14 @@ else: { . cf_error("Unknown character"); \n { - conf_lino++; + ifs->conf_lino++; BEGIN(INITIAL); } . \*\/ BEGIN(INITIAL); -\n conf_lino++; +\n ifs->conf_lino++; \/\* cf_error("Comment nesting not supported"); <> cf_error("Unterminated comment"); . @@ -234,6 +244,50 @@ cf_hash(byte *c) return h; } +/* Open included file with properly swapped buffers */ +static void +new_include(void) +{ + char *fname, *p = NULL; + + if ((fname = strchr(yytext, '"')) != NULL) { + + if ((p = strchr(++fname, '"')) != NULL) *p = '\0'; + + if (ifs_depth >= MAX_INCLUDE_DEPTH) + cf_error("Max include depth reached."); + + /* 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); + + yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); + } +} + +static int +check_eof(void) +{ + if (ifs == ifs_head) { + /* EOF in main config file */ + ifs->conf_lino = 1; + 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; +} + static struct symbol * cf_new_sym(byte *c, unsigned int h) { @@ -359,6 +413,16 @@ 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 @@ -367,11 +431,18 @@ cf_lex_init_kh(void) * parsing of a new input. */ void -cf_lex_init(int is_cli) +cf_lex_init(int is_cli, struct config *c) { if (!kw_hash_inited) cf_lex_init_kh(); - conf_lino = 1; + 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); + } yyrestart(NULL); if (is_cli) BEGIN(CLI); diff --git a/conf/conf.c b/conf/conf.c index 604a412c..5bdeece2 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -108,7 +108,7 @@ config_parse(struct config *c) cfg_mem = c->mem; if (setjmp(conf_jmpbuf)) return 0; - cf_lex_init(0); + cf_lex_init(0, c); sysdep_preconfig(c); protos_preconfig(c); rt_preconfig(c); @@ -138,7 +138,7 @@ cli_parse(struct config *c) cfg_mem = c->mem; if (setjmp(conf_jmpbuf)) return 0; - cf_lex_init(1); + cf_lex_init(1, c); cf_parse(); return 1; } @@ -355,7 +355,8 @@ cf_error(char *msg, ...) if (bvsnprintf(buf, sizeof(buf), msg, args) < 0) strcpy(buf, ""); new_config->err_msg = cfg_strdup(buf); - new_config->err_lino = conf_lino; + new_config->err_lino = ifs->conf_lino; + new_config->err_file_name = ifs->conf_fname; longjmp(conf_jmpbuf, 1); } diff --git a/conf/conf.h b/conf/conf.h index 6c784d7e..df772681 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -12,6 +12,8 @@ #include "lib/resource.h" #include "lib/timer.h" +#define BIRD_FNAME_MAX 255 /* Would be better to use some UNIX define */ + /* Configuration structure */ struct config { @@ -38,7 +40,9 @@ struct config { int cli_debug; /* Tracing of CLI connections and commands */ char *err_msg; /* Parser error message */ int err_lino; /* Line containing error */ - char *file_name; /* Name of configuration file */ + char *err_file_name; /* File name containing error */ + char *file_name; /* Name of main configuration file */ + int file_fd; /* File descriptor of main configuration file */ struct symbol **sym_hash; /* Lexer: symbol hash table */ struct symbol **sym_fallback; /* Lexer: fallback symbol hash table */ int obstacle_count; /* Number of items blocking freeing of this config */ @@ -83,7 +87,8 @@ char *cfg_strdup(char *c); /* Lexer */ -extern int (*cf_read_hook)(byte *buf, unsigned int max); +extern int (*cf_read_hook)(byte *buf, unsigned int max, int fd); +extern int (*cf_open_hook)(char *filename); struct symbol { struct symbol *next; @@ -106,10 +111,20 @@ struct symbol { #define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */ -extern int conf_lino; +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; +}; + +struct include_file_stack *ifs; + int cf_lex(void); -void cf_lex_init(int is_cli); +void cf_lex_init(int is_cli, struct config *c); struct symbol *cf_find_symbol(byte *c); struct symbol *cf_default_name(char *template, int *counter); struct symbol *cf_define_symbol(struct symbol *symbol, int type, void *def); diff --git a/doc/bird.conf.example b/doc/bird.conf.example index 7019aba3..36aa21a9 100644 --- a/doc/bird.conf.example +++ b/doc/bird.conf.example @@ -25,6 +25,8 @@ #filter sink { reject; } #filter okay { accept; } +#include "filters.conf"; + # Define another routing table #table testable; diff --git a/doc/bird.sgml b/doc/bird.sgml index a00062e8..f4a78296 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -259,6 +259,9 @@ protocol rip { Global options

+ include " + This statement causes inclusion of a new file. The maximal depth is set to 5. + log " Set logging of messages having the given class (either code = ret->aux = 0; ret->arg1 = ret->arg2 = ret->next = NULL; - ret->lineno = conf_lino; + ret->lineno = ifs->conf_lino; return ret; } diff --git a/filter/test.conf b/filter/test.conf index 19372f24..4f09637c 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -329,6 +329,9 @@ string s; test_undef(3); test_undef(2); + print "Testing include"; + include "test.conf.inc"; + print "done"; quitbird; # print "*** FAIL: this is unreachable"; diff --git a/filter/test.conf.inc b/filter/test.conf.inc new file mode 100644 index 00000000..10fc7014 --- /dev/null +++ b/filter/test.conf.inc @@ -0,0 +1,6 @@ + +print "Entering include"; +print "Should be 2: ", 1+1; +print "Leaving include"; + + diff --git a/nest/cli.c b/nest/cli.c index a61518b5..c83a54fe 100644 --- a/nest/cli.c +++ b/nest/cli.c @@ -251,6 +251,7 @@ 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; diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index 55477913..72f7e852 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "nest/bird.h" #include "lib/lists.h" @@ -150,18 +151,34 @@ read_iproute_table(char *file, char *prefix, int max) #endif // PATH_IPROUTE_DIR -static int conf_fd; static char *config_name = PATH_CONFIG; static int -cf_read(byte *dest, unsigned int len) +cf_read(byte *dest, unsigned int len, int fd) { - int l = read(conf_fd, dest, len); + int l = read(fd, dest, len); if (l < 0) cf_error("Read error"); return l; } +static int +cf_open(char *filename) +{ + char full_name[BIRD_FNAME_MAX]; + char *cur = filename; + int ret; + + if (*filename != '/') { + snprintf(full_name, sizeof(full_name), "%s/%s", dirname(config_name), filename); + cur = full_name; + } + + if ((ret = open(cur, O_RDONLY)) == -1) + cf_error("Unable to open included configuration file: %s", cur); +} + + void sysdep_preconfig(struct config *c) { @@ -189,12 +206,13 @@ unix_read_config(struct config **cp, char *name) int ret; *cp = conf; - conf_fd = open(name, O_RDONLY); - if (conf_fd < 0) + conf->file_fd = open(name, O_RDONLY); + if (conf->file_fd < 0) return 0; cf_read_hook = cf_read; + cf_open_hook = cf_open; ret = config_parse(conf); - close(conf_fd); + close(conf->file_fd); return ret; } @@ -206,7 +224,7 @@ read_config(void) if (!unix_read_config(&conf, config_name)) { if (conf->err_msg) - die("%s, line %d: %s", config_name, conf->err_lino, conf->err_msg); + die("%s, line %d: %s", conf->err_file_name, conf->err_lino, conf->err_msg); else die("Unable to open configuration file %s: %m", config_name); } @@ -222,7 +240,7 @@ async_config(void) if (!unix_read_config(&conf, config_name)) { if (conf->err_msg) - log(L_ERR "%s, line %d: %s", config_name, conf->err_lino, conf->err_msg); + log(L_ERR "%s, line %d: %s", conf->err_file_name, conf->err_lino, conf->err_msg); else log(L_ERR "Unable to open configuration file %s: %m", config_name); config_free(conf);