]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit '58efa944' into thread-next
authorMaria Matejka <mq@ucw.cz>
Wed, 25 Oct 2023 12:41:11 +0000 (14:41 +0200)
committerMaria Matejka <mq@ucw.cz>
Wed, 25 Oct 2023 12:41:11 +0000 (14:41 +0200)
Conflicts:
conf/cf-lex.l
conf/conf.h
conf/confbase.Y
conf/gen_keywords.m4
conf/gen_parser.m4
filter/config.Y
nest/config.Y
proto/bgp/config.Y
proto/static/config.Y

Keywords and attributes are split to separate namespaces, to avoid
collisions between regular keyword use and attribute overlay.

1  2 
conf/cf-lex.l
conf/conf.h
conf/confbase.Y
conf/gen_keywords.m4
filter/config.Y
nest/config.Y
proto/bgp/config.Y
proto/static/config.Y

diff --cc conf/cf-lex.l
index 1b49b5a3fe83155ebad2a6b876579410976f292e,fb2ffb325745ef553853ff3039ef3e583d8917ce..85f43c369b3b919ad643058277338b1c16774ab5
@@@ -85,14 -78,10 +78,20 @@@ static uint cf_hash(const byte *c)
  
  HASH_DEFINE_REHASH_FN(SYM, struct symbol)
  
- HASH(struct keyword) kw_hash;
- HASH(struct ea_class) ea_name_hash;
 -struct sym_scope *conf_this_scope;
 -struct sym_scope *global_root_scope;
++/* Global symbol scopes */
+ static pool *global_root_scope_pool;
++static struct sym_scope
++  global_root_scope = {
++    .active = 1,
++  },
++  global_filter_scope = {
++    .active = 0,
++    .next = &global_root_scope,
++  };
++
++/* Local symbol scope: TODO this isn't thread-safe */
 +struct sym_scope *conf_this_scope;
  
- static struct sym_scope global_root_scope__init = { .active = 1, };
- struct sym_scope *global_root_scope = &global_root_scope__init;
  linpool *cfg_mem;
  
  int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
@@@ -579,14 -568,19 +578,14 @@@ cf_new_symbol(const byte *c
  
    cf_swap_soft_scope();
  
 -  pool *p = new_config->pool;
 -
 -  if (conf_this_scope == global_root_scope)
 -    s = mb_allocz(p = global_root_scope_pool, sizeof(struct symbol) + l + 1);
 -  else
 -    s = cfg_allocz(sizeof(struct symbol) + l + 1);
 +  s = cfg_allocz(sizeof(struct symbol) + l + 1);
    *s = (struct symbol) { .scope = conf_this_scope, .class = SYM_VOID, };
--  strcpy(s->name, c);
++  memcpy(s->name, c, l+1);
  
    if (!conf_this_scope->hash.data)
 -    HASH_INIT(conf_this_scope->hash, p, SYM_ORDER);
 +    HASH_INIT(conf_this_scope->hash, new_config->pool, SYM_ORDER);
  
 -  HASH_INSERT2(conf_this_scope->hash, SYM, p, s);
 +  HASH_INSERT2(conf_this_scope->hash, SYM, new_config->pool, s);
  
    if (conf_this_scope == new_config->root_scope)
      add_tail(&(new_config->symbols), &(s->n));
    return s;
  }
  
 -struct symbol *
 -cf_symbol_from_keyword(const struct keyword *kw)
 -{ return cf_new_symbol(kw->name); }
 +static struct symbol *
- cf_root_symbol(const byte *c)
++cf_root_symbol(const byte *c, struct sym_scope *ss)
 +{
 +  uint l = strlen(c);
 +  if (l > SYM_MAX_LEN)
 +    bug("Root symbol %s too long", c);
 +
 +  struct symbol *s = mb_alloc(&root_pool, sizeof(struct symbol) + l + 1);
-   *s = (struct symbol) { .scope = global_root_scope, .class = SYM_VOID, };
++  *s = (struct symbol) { .scope = ss, .class = SYM_VOID, };
 +  memcpy(s->name, c, l+1);
 +
-   if (!global_root_scope->hash.data)
-     HASH_INIT(global_root_scope->hash, &root_pool, SYM_ORDER);
++  if (!ss->hash.data)
++    HASH_INIT(ss->hash, &root_pool, SYM_ORDER);
 +
-   HASH_INSERT2(global_root_scope->hash, SYM, &root_pool, s);
++  HASH_INSERT2(ss->hash, SYM, &root_pool, s);
 +  return s;
 +}
 +
  
  /**
   * cf_find_symbol_scope - find a symbol by name
@@@ -631,7 -610,7 +630,7 @@@ cf_find_symbol_scope(const struct sym_s
  
    /* Find the symbol here or anywhere below */
    while (scope)
-     if (scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c, 1)))
 -    if (scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
++    if (scope->active && scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
        return s;
      else
        scope = scope->next;
@@@ -702,64 -681,22 +701,52 @@@ cf_lex_symbol(const char *data
    struct symbol *sym = cf_get_symbol(data);
    cf_lval.s = sym;
  
-   /* Is it a keyword? Prefer the keyword. */
-   struct keyword *k = HASH_FIND(kw_hash, KW, data);
-   if (k)
+   switch (sym->class)
    {
-     if (k->value > 0)
-       return k->value;
-     else
-     {
-       cf_lval.i = -k->value;
-       return ENUM;
-     }
+     case SYM_KEYWORD:
 -    {
 -      int val = sym->keyword->value;
 -      if (val > 0) return val;
 -      cf_lval.i = -val;
 -      return ENUM;
 -    }
++      if (sym->keyword->value > 0)
++      return sym->keyword->value;
++      else
++      {
++      cf_lval.i = -sym->keyword->value;
++      return ENUM;
++      }
+     case SYM_VOID:
+       return CF_SYM_UNDEFINED;
+     default:
+       return CF_SYM_KNOWN;
    }
-   /* OK, only a symbol. */
-   if (sym->class == SYM_VOID)
-     return CF_SYM_UNDEFINED;
-   else
-     return CF_SYM_KNOWN;
- }
- static void
- cf_lex_init_kh(void)
- {
-   HASH_INIT(kw_hash, config_pool, KW_ORDER);
-   struct keyword *k;
-   for (k=keyword_list; k->name; k++)
-     HASH_INSERT(kw_hash, KW, k);
  }
  
-   struct symbol *sym = cf_root_symbol(def->name);
-   sym->class = SYM_ATTRIBUTE;
-   sym->attribute = def;
-   def->sym = sym;
 +void
 +ea_lex_register(struct ea_class *def)
 +{
-   HASH_REMOVE2(global_root_scope->hash, SYM, &root_pool, sym);
++  def->sym = cf_define_symbol(cf_root_symbol(def->name, &global_filter_scope), SYM_ATTRIBUTE, attribute, def);
 +}
 +
 +void
 +ea_lex_unregister(struct ea_class *def)
 +{
 +  struct symbol *sym = def->sym;
-   struct symbol *sym = cf_find_symbol(global_root_scope, name);
++  HASH_REMOVE2(global_filter_scope.hash, SYM, &root_pool, sym);
 +  mb_free(sym);
 +  def->sym = NULL;
 +}
 +
 +struct ea_class *
 +ea_class_find_by_name(const char *name)
 +{
++  if (!global_filter_scope.hash.data)
++    return NULL;
++
++  struct symbol *sym = HASH_FIND(global_filter_scope.hash, SYM, name);
++
 +  if (!sym || (sym->class != SYM_ATTRIBUTE))
 +    return NULL;
 +  else
 +    return sym->attribute;
 +}
 +
  /**
   * cf_lex_init - initialize the lexer
   * @is_cli: true if we're going to parse CLI command, false for configuration
  void
  cf_lex_init(int is_cli, struct config *c)
  {
-   if (!kw_hash.data)
-     cf_lex_init_kh();
+   if (!global_root_scope_pool)
+   {
 -    global_root_scope_pool = rp_new(&root_pool, "Keywords pool");
 -    conf_this_scope = global_root_scope = mb_allocz(global_root_scope_pool, sizeof(*global_root_scope));
++    global_root_scope_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Keywords pool");
+     for (const struct keyword *k = keyword_list; k->name; k++)
 -      cf_define_symbol(cf_get_symbol(k->name), SYM_KEYWORD, keyword, k);
++      cf_define_symbol(cf_root_symbol(k->name, &global_root_scope), SYM_KEYWORD, keyword, k);
+   }
  
    ifs_head = ifs = push_ifs(NULL);
    if (!is_cli)
    if (is_cli)
      conf_this_scope->next = config->root_scope;
    else
--    conf_this_scope->next = global_root_scope;
++    conf_this_scope->next = &global_filter_scope;
  }
  
  /**
@@@ -886,6 -829,6 +878,27 @@@ cf_swap_soft_scope(void
    }
  }
  
++/**
++ * cf_enter_filters - enable filter / route attributes namespace
++ */
++void
++cf_enter_filters(void)
++{
++  ASSERT_DIE(!global_filter_scope.active);
++  global_filter_scope.active = 1;
++}
++
++/**
++ * cf_exit_filters - disable filter / route attributes namespace
++ */
++void
++cf_exit_filters(void)
++{
++  ASSERT_DIE(global_filter_scope.active);
++  global_filter_scope.active = 0;
++}
++
++
  /**
   * cf_symbol_class_name - get name of a symbol class
   * @sym: symbol
@@@ -912,6 -855,6 +925,8 @@@ cf_symbol_class_name(struct symbol *sym
        return "routing table";
      case SYM_ATTRIBUTE:
        return "custom attribute";
++    case SYM_KEYWORD:
++      return "symbol";
      case SYM_CONSTANT_RANGE:
        return "constant";
      case SYM_VARIABLE_RANGE:
diff --cc conf/conf.h
index 58a2733b6de2ecf7f2ef1f3128648ecc6aac03c0,12f1a6b3948d5c9fc5b8b783279d42686a9597f1..19663b3f665ab22019274a781fcf3e13b32a3f87
@@@ -124,9 -120,10 +124,10 @@@ struct symbol 
      const struct f_line *function;    /* For SYM_FUNCTION */
      const struct filter *filter;      /* For SYM_FILTER */
      struct rtable_config *table;      /* For SYM_TABLE */
 -    struct f_dynamic_attr *attribute; /* For SYM_ATTRIBUTE */
 +    struct ea_class *attribute;               /* For SYM_ATTRIBUTE */
      struct f_val *val;                        /* For SYM_CONSTANT */
      uint offset;                      /* For SYM_VARIABLE */
+     const struct keyword *keyword;    /* For SYM_KEYWORD */
    };
  
    char name[0];
@@@ -144,7 -141,7 +145,8 @@@ struct sym_scope 
    byte soft_scopes;                   /* Number of soft scopes above */
  };
  
--extern struct sym_scope *global_root_scope;
++void cf_enter_filters(void);
++void cf_exit_filters(void);
  
  struct bytestring {
    size_t length;
diff --cc conf/confbase.Y
index 40ea16de30096f31eff0df2b4305a24bdeeb1af6,0c2807dc5f5db3fc7185af07eb96b071615080f0..c92d4810e11db4fcba56ca01f27317f0d97bcc1c
@@@ -121,7 -118,7 +121,7 @@@ CF_DECL
  
  %type <t> text opttext
  %type <bs> bytestring
- %type <s> symbol symbol_known toksym 
 -%type <s> symbol symbol_known
++%type <s> symbol
  
  %type <v> bytestring_text text_or_ipa
  %type <x> bytestring_expr
@@@ -169,7 -166,7 +169,7 @@@ definition
  expr:
     NUM
   | '(' term ')' { $$ = cf_eval_int($2); }
-- | symbol_known {
++ | CF_SYM_KNOWN {
       if ($1->class != (SYM_CONSTANT | T_INT)) cf_error("Number constant expected");
       $$ = SYM_VAL($1).i; }
   ;
@@@ -180,9 -177,8 +180,7 @@@ expr_us
   | expr US { $$ = $1 US_; }
   ;
  
- toksym: FROM | PREFERENCE ;
- symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN | toksym ;
- symbol_known: CF_SYM_KNOWN | toksym ;
 -symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN | KEYWORD ;
 -symbol_known: CF_SYM_KNOWN | KEYWORD ;
++symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN ;
  
  /* Switches */
  
index 53226e4dad5153dab9e30a61d3a89dc488c6bf70,4e8651f6d426d3914594e66ef445c21e146cc488..5667ceaaac9353c55f3deac22b61818b44abb032
@@@ -39,11 -40,11 +39,11 @@@ m4_divert(-1)'
  m4_define(CF_ENUM, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$2)CF_iterate([[CF_enum]], [[m4_shift(m4_shift($@))]])DNL')
  m4_define(CF_ENUM_PX, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$3)CF_iterate([[CF_enum]], [[m4_shift(m4_shift(m4_shift($@)))]])DNL')
  
--# After all configuration templates end, we generate the 
++# After all configuration templates end, we generate the keyword list
  m4_m4wrap(`
  m4_divert(0)
- static struct keyword keyword_list[] = {
- m4_undivert(1){ NULL, -1, NULL } };
+ static const struct keyword keyword_list[] = {
+ m4_undivert(1){ NULL, -1 } };
  ')
  
  # As we are processing C source, we must access all M4 primitives via
diff --cc filter/config.Y
index 194e312db8c653540f2404f287cb5287b83ea73e,fcdbb4a9df700735cd690b971f55002dd5d9763a..671ccb9d86c6e68de350fa8f19bf73e29b0ec005
@@@ -356,21 -349,15 +356,25 @@@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UN
  
  CF_GRAMMAR
  
 +conf: FILTER STACKS expr expr ';' {
 +  new_config->filter_vstk = $3;
 +  new_config->filter_estk = $4;
 + }
 + ;
 +
  conf: filter_def ;
  filter_def:
--   FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
--     filter_body {
++   FILTER symbol {
++     $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL);
++     cf_enter_filters();
++     cf_push_scope( $2 );
++   } filter_body {
       struct filter *f = cfg_alloc(sizeof(struct filter));
       *f = (struct filter) { .sym = $2, .root = $4 };
       $2->filter = f;
  
       cf_pop_scope();
++     cf_exit_filters();
     }
   ;
  
@@@ -393,7 -373,7 +397,7 @@@ custom_attr: ATTRIBUTE type symbol ';' 
  
  conf: bt_test_suite ;
  bt_test_suite:
-- BT_TEST_SUITE '(' symbol_known ',' text ')' {
++ BT_TEST_SUITE '(' CF_SYM_KNOWN ',' text ')' {
    cf_assert_symbol($3, SYM_FUNCTION);
    struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
    t->fn = $3->function;
  
  conf: bt_test_same ;
  bt_test_same:
-- BT_TEST_SAME '(' symbol_known ',' symbol_known ',' NUM ')' {
++ BT_TEST_SAME '(' CF_SYM_KNOWN ',' CF_SYM_KNOWN ',' NUM ')' {
    cf_assert_symbol($3, SYM_FUNCTION);
    cf_assert_symbol($5, SYM_FUNCTION);
    struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
@@@ -488,23 -468,23 +492,30 @@@ function_vars
  filter_body: function_body ;
  
  filter:
--   symbol_known {
++   CF_SYM_KNOWN {
       cf_assert_symbol($1, SYM_FILTER);
       $$ = $1->filter;
     }
-- | { cf_push_scope(NULL); } filter_body {
++ | {
++     cf_enter_filters();
++     cf_push_scope(NULL);
++   } filter_body {
       struct filter *f = cfg_alloc(sizeof(struct filter));
       *f = (struct filter) { .root = $2 };
       $$ = f;
  
       cf_pop_scope();
++     cf_exit_filters();
     }
   ;
  
  where_filter:
--   WHERE term {
++   WHERE {
++     cf_enter_filters();
++   } term {
       /* Construct 'IF term THEN { ACCEPT; } ELSE { REJECT; }' */
--     $$ = f_new_where($2);
++     $$ = f_new_where($3);
++     cf_exit_filters();
     }
   ;
  
@@@ -520,6 -500,6 +531,7 @@@ function_def
     FUNCTION symbol {
       DBG( "Beginning of function %s\n", $2->name );
       $2 = cf_define_symbol($2, SYM_FUNCTION, function, NULL);
++     cf_enter_filters();
       cf_push_scope($2);
     } function_args {
       /* Make dummy f_line for storing function prototype */
       $6->arg_list = $2->function->arg_list;
       $2->function = $6;
       cf_pop_scope();
++     cf_exit_filters();
     }
   ;
  
@@@ -598,10 -578,10 +611,10 @@@ set_atom
   | VPN_RD { $$.type = T_RD; $$.val.ec = $1; }
   | ENUM   { $$.type = pair_a($1); $$.val.i = pair_b($1); }
   | '(' term ')' {
 -     $$ = cf_eval($2, T_VOID);
 +     $$ = cf_eval_tmp($2, T_VOID);
       if (!f_valid_set_type($$.type)) cf_error("Set-incompatible type");
     }
-- | symbol_known {
++ | CF_SYM_KNOWN {
       cf_assert_symbol($1, SYM_CONSTANT);
       if (!f_valid_set_type(SYM_TYPE($1))) cf_error("%s: set-incompatible type", $1->name);
       $$ = *$1->val;
@@@ -773,7 -753,7 +786,7 @@@ var_list: /* EMPTY */ { $$ = NULL; 
   | var_list ',' term { $$ = $3; $$->next = $1; }
  
  function_call:
--   symbol_known '(' var_list ')'
++   CF_SYM_KNOWN '(' var_list ')'
     {
       if ($1->class != SYM_FUNCTION)
         cf_error("You can't call something which is not a function. Really.");
     }
   ;
  
- symbol_value: symbol_known 
 -symbol_value: symbol_known
++symbol_value: CF_SYM_KNOWN
    {
      switch ($1->class) {
        case SYM_CONSTANT_RANGE:
@@@ -951,7 -938,7 +964,7 @@@ cmd
       $$ = f_new_inst(FI_FOR_INIT, $6, $3);
       $$->next = f_new_inst(FI_FOR_NEXT, $3, $9);
     }
-- | symbol_known '=' term ';' {
++ | CF_SYM_KNOWN '=' term ';' {
       switch ($1->class) {
         case SYM_VARIABLE_RANGE:
         $$ = f_new_inst(FI_VAR_SET, $3, $1);
         cf_error( "This static attribute is read-only.");
       $$ = f_new_inst(FI_RTA_SET, $3, $1);
     }
-  | UNSET '(' symbol_known ')' ';' {
 - | UNSET '(' dynamic_attr ')' ';' {
 -     $$ = f_new_inst(FI_EA_UNSET, $3);
++ | UNSET '(' CF_SYM_KNOWN ')' ';' {
 +     if ($3->class != SYM_ATTRIBUTE)
 +       cf_error("Can't unset %s", $3->name);
 +     if ($3->attribute->readonly)
 +       cf_error("Attribute %s is read-only", $3->attribute->name);
 +     $$ = f_new_inst(FI_EA_UNSET, $3->attribute);
     }
   | break_command print_list ';' {
      struct f_inst *breaker = f_new_inst(FI_DIE, $1);
        $$ = f_new_inst(FI_SWITCH, $2, $4);
     }
  
-  | symbol_known '.' EMPTY ';' { $$ = f_generate_empty($1); }
-  | symbol_known '.' PREPEND '(' term ')' ';'   { $$ = f_generate_complex_sym( FI_PATH_PREPEND, $1, $5 ); }
-  | symbol_known '.' ADD '(' term ')' ';'       { $$ = f_generate_complex_sym( FI_CLIST_ADD, $1, $5 ); }
-  | symbol_known '.' DELETE '(' term ')' ';'    { $$ = f_generate_complex_sym( FI_CLIST_DEL, $1, $5 ); }
-  | symbol_known '.' FILTER '(' term ')' ';'    { $$ = f_generate_complex_sym( FI_CLIST_FILTER, $1, $5 ); }
 - | dynamic_attr '.' EMPTY ';' { $$ = f_new_inst(FI_EA_SET, f_const_empty($1), $1); }
 - | dynamic_attr '.' PREPEND '(' term ')' ';'   { $$ = f_generate_complex( FI_PATH_PREPEND, $1, $5 ); }
 - | dynamic_attr '.' ADD '(' term ')' ';'       { $$ = f_generate_complex( FI_CLIST_ADD, $1, $5 ); }
 - | dynamic_attr '.' DELETE '(' term ')' ';'    { $$ = f_generate_complex( FI_CLIST_DEL, $1, $5 ); }
 - | dynamic_attr '.' FILTER '(' term ')' ';'    { $$ = f_generate_complex( FI_CLIST_FILTER, $1, $5 ); }
++ | CF_SYM_KNOWN '.' EMPTY ';' { $$ = f_generate_empty($1); }
++ | CF_SYM_KNOWN '.' PREPEND '(' term ')' ';'   { $$ = f_generate_complex_sym( FI_PATH_PREPEND, $1, $5 ); }
++ | CF_SYM_KNOWN '.' ADD '(' term ')' ';'       { $$ = f_generate_complex_sym( FI_CLIST_ADD, $1, $5 ); }
++ | CF_SYM_KNOWN '.' DELETE '(' term ')' ';'    { $$ = f_generate_complex_sym( FI_CLIST_DEL, $1, $5 ); }
++ | CF_SYM_KNOWN '.' FILTER '(' term ')' ';'    { $$ = f_generate_complex_sym( FI_CLIST_FILTER, $1, $5 ); }
   | BT_ASSERT '(' get_cf_position term get_cf_position ')' ';' { $$ = assert_done($4, $3 + 1, $5 - 1); }
   | BT_CHECK_ASSIGN '(' get_cf_position lvalue get_cf_position ',' term ')' ';' { $$ = assert_assign(&$4, $7, $3 + 1, $5 - 1); }
   ;
@@@ -1019,17 -1003,8 +1032,17 @@@ get_cf_position
  };
  
  lvalue:
-    symbol_known {
 -   CF_SYM_KNOWN { cf_assert_symbol($1, SYM_VARIABLE); $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 }; }
++   CF_SYM_KNOWN {
 +      switch ($1->class) {
 +      case SYM_VARIABLE_RANGE:
 +        $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 };
 +        break;
 +      case SYM_ATTRIBUTE:
 +        $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1->attribute };
 +        break;
 +      }
 +  }
   | static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
 - | dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1 }; };
 + ;
  
  CF_END
diff --cc nest/config.Y
index e186c85164b30df0d8d647e56786c5840d0bb543,8aaf0128fc90e290c0b76e20193751f4a292fad0..38594f3398a3deaa3bc352e06dcba1e86ac82346
@@@ -692,11 -664,10 +690,11 @@@ r_args
       if ($$->addr) cf_error("Only one prefix expected");
       if (!net_type_match($3, NB_IP)) cf_error("Only IP networks accepted for 'in' argument");
       $$->addr = $3;
 -     $$->addr_mode = RSD_ADDR_IN;
 +     $$->addr_mode = TE_ADDR_IN;
     }
--| r_args TABLE symbol_known {
++| r_args TABLE CF_SYM_KNOWN {
       cf_assert_symbol($3, SYM_TABLE);
 +     if (!$3->table) cf_error("Table %s not configured", $3->name);
       $$ = $1;
       rt_show_add_table($$, $3->table->table);
       $$->tables_defined_by = RSD_TDB_DIRECT;
       $$ = $1;
       $$->filtered = 1;
     }
-- | r_args export_mode symbol_known {
++ | r_args export_mode CF_SYM_KNOWN {
       cf_assert_symbol($3, SYM_PROTO);
       struct proto_config *c = (struct proto_config *) $3->proto;
       $$ = $1;
       $$->export_channel = $3;
       $$->tables_defined_by = RSD_TDB_INDIRECT;
     }
-- | r_args PROTOCOL symbol_known {
++ | r_args PROTOCOL CF_SYM_KNOWN {
       cf_assert_symbol($3, SYM_PROTO);
       struct proto_config *c = (struct proto_config *) $3->proto;
       $$ = $1;
Simple merge
index 9d26ee82bb8d96b22150afa3480f8e0b3cca4b1a,9d26ee82bb8d96b22150afa3480f8e0b3cca4b1a..f4b31f3f3421438174e0d33c93ba541f66f1df20
@@@ -153,7 -153,7 +153,7 @@@ stat_route_opts
  
  stat_route_opt_list:
     /* empty */
-- | '{' stat_route_opts '}'
++ | '{' { cf_enter_filters(); } stat_route_opts '}' { cf_exit_filters(); }
   ;