]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit 'fc9d471b' into thread-next
authorMaria Matejka <mq@ucw.cz>
Sat, 28 Oct 2023 21:42:21 +0000 (23:42 +0200)
committerMaria Matejka <mq@ucw.cz>
Sat, 28 Oct 2023 21:42:21 +0000 (23:42 +0200)
Conflicts:
conf/cf-lex.l
conf/conf.h
filter/config.Y
filter/data.c
filter/data.h

17 files changed:
1  2 
conf/cf-lex.l
conf/conf.h
conf/confbase.Y
conf/gen_parser.m4
doc/bird.sgml
filter/config.Y
filter/data.c
filter/data.h
filter/decl.m4
filter/f-inst.c
filter/f-inst.h
filter/test.conf
proto/bfd/config.Y
proto/bgp/config.Y
proto/ospf/config.Y
proto/rip/config.Y
sysdep/unix/log.c

diff --cc conf/cf-lex.l
index 179b66a492504aad068e09cd0ec946b94c8c2731,c4760e4037993741f9cac1f11de2a2afa1380951..20399165515f5adc949e9bed6d175e616b9bd218
@@@ -78,19 -78,9 +78,20 @@@ static uint cf_hash(const byte *c)
  
  HASH_DEFINE_REHASH_FN(SYM, struct symbol)
  
 -struct sym_scope *global_root_scope;
 +/* Global symbol scopes */
- static pool *global_root_scope_pool;
+ pool *global_root_scope_pool;
+ linpool *global_root_scope_linpool;
 +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;
  
  linpool *cfg_mem;
  
@@@ -594,25 -584,6 +595,31 @@@ cf_new_symbol(struct sym_scope *scope, 
    return s;
  }
  
- static struct symbol *
++struct symbol *
 +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);
++  if (!global_root_scope_pool)
++  {
++    global_root_scope_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Keywords pool");
++    global_root_scope_linpool = lp_new(global_root_scope_pool);
++  }
++
++  struct symbol *s = lp_alloc(global_root_scope_linpool, sizeof(struct symbol) + l + 1);
 +  *s = (struct symbol) { .scope = ss, .class = SYM_VOID, };
 +  memcpy(s->name, c, l+1);
 +
 +  if (!ss->hash.data)
 +    HASH_INIT(ss->hash, &root_pool, SYM_ORDER);
 +
 +  HASH_INSERT2(ss->hash, SYM, &root_pool, s);
 +  return s;
 +}
 +
 +
  /**
   * cf_find_symbol_scope - find a symbol by name
   * @scope: config scope
@@@ -722,37 -695,8 +731,39 @@@ cf_lex_symbol(const char *data
    }
  }
  
 +void
 +ea_lex_register(struct ea_class *def)
 +{
 +  def->sym = cf_root_symbol(def->name, &global_filter_scope);
 +  def->sym->class = SYM_ATTRIBUTE;
 +  def->sym->attribute = def;
 +}
 +
 +void
 +ea_lex_unregister(struct ea_class *def)
 +{
 +  struct symbol *sym = def->sym;
 +  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;
 +}
 +
+ void f_type_methods_register(void);
  /**
   * 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 (!global_root_scope_pool)
++  if (!global_root_scope.readonly)
    {
-     global_root_scope_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Keywords pool");
 -    global_root_scope_pool = rp_new(&root_pool, "Keywords pool");
 -    global_root_scope_linpool = lp_new(global_root_scope_pool);
 -    global_root_scope = lp_allocz(global_root_scope_linpool, sizeof(*global_root_scope));
--
      for (const struct keyword *k = keyword_list; k->name; k++)
      {
 -      struct symbol *sym = cf_new_symbol(global_root_scope, global_root_scope_pool, global_root_scope_linpool, k->name);
 -      sym->class = SYM_KEYWORD;
 -      sym->keyword = k;
 +      struct symbol *s = cf_root_symbol(k->name, &global_root_scope);
 +      s->class = SYM_KEYWORD;
 +      s->keyword = k;
      }
  
 -    global_root_scope->readonly = 1;
 +    global_root_scope.readonly = 1;
 +    global_filter_scope.readonly = 1;
+     f_type_methods_register();
    }
  
    ifs_head = ifs = push_ifs(NULL);
diff --cc conf/conf.h
index f7833d9e168834e13368c1c6d5dadc428c0b6043,1413cb581bd09fff80ec7191e0bf38b4ce6a3cd0..762ba3c1363d611386396ab8fd012fc4de0c72b6
@@@ -147,9 -145,10 +149,12 @@@ struct sym_scope 
    byte readonly:1;                    /* Do not add new symbols */
  };
  
 -extern struct sym_scope *global_root_scope;
 +void cf_enter_filters(void);
 +void cf_exit_filters(void);
 +
+ extern pool *global_root_scope_pool;
+ extern linpool *global_root_scope_linpool;
  struct bytestring {
    size_t length;
    byte data[];
@@@ -213,6 -213,9 +219,10 @@@ struct symbol *cf_localize_symbol(struc
  static inline int cf_symbol_is_local(struct config *conf, struct symbol *sym)
  { return (sym->scope == conf->current_scope) && !conf->current_scope->soft_scopes; }
  
+ /* internal */
+ struct symbol *cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c);
++struct symbol *cf_root_symbol(const byte *, struct sym_scope *);
  /**
   * cf_define_symbol - define meaning of a symbol
   * @sym: symbol to be defined
diff --cc conf/confbase.Y
Simple merge
index af4b14554c71b26b9a557f32ae6134139a5de0b0,80071aefa19bce7287288dcf04c2e73a0b403a2f..b7167d7a5f0386ff99f530f0d931af476483e788
@@@ -29,11 -29,19 +29,13 @@@ m4_define(CF_END, `m4_divert(-1)'
  m4_define(CF_itera, `m4_ifelse($#, 1, [[CF_iter($1)]], [[CF_iter($1)[[]]CF_itera(m4_shift($@))]])')
  m4_define(CF_iterate, `m4_define([[CF_iter]], m4_defn([[$1]]))CF_itera($2)')
  
 -m4_define(CF_append, `m4_define([[$1]], m4_ifdef([[$1]], m4_defn([[$1]])[[$3]])[[$2]])')
 -
 -# Keywords act as %token<s>
 -m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_append([[CF_kw_rule]],$1,[[ | ]])m4_define([[CF_toks]],CF_toks $1)]])')
 +# Keywords act as untyped %token
 +m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])')
  m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
  )DNL')
+ m4_define(CF_METHODS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
+ )DNL')
  
 -m4_define(CF_keywd2, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])')
 -m4_define(CF_KEYWORDS_EXCLUSIVE, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd2]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
 -)DNL')
 -
  # CLI commands
  m4_define(CF_CLI, `m4_define([[CF_cmd]], cmd_[[]]m4_translit($1, [[ ]], _))DNL
  m4_divert(2)CF_KEYWORDS(m4_translit($1, [[ ]], [[,]]))
diff --cc doc/bird.sgml
Simple merge
diff --cc filter/config.Y
index aededce0e51de79f9cbc4a7887201fb5ac1bff4f,c15814fd3905b8c7dd4c27cf6e7bb8757d0b273b..4ce9ee0f734d856189defdacc0e00156c7eaef22
@@@ -209,33 -244,21 +244,19 @@@ f_new_lc_item(u32 f1, u32 t1, u32 f2, u
    return t;
  }
  
- static inline struct f_inst *
- f_generate_empty(const struct symbol *sym)
- {
-   if (sym->class != SYM_ATTRIBUTE)
-     cf_error("Can't empty %s: not an attribute", sym->name);
-   const struct ea_class *def = sym->attribute;
-   const struct f_val empty = f_get_empty(def->type);
-   if (empty.type == T_VOID)
-     cf_error("Can't empty attribute %s", def->name);
-   return f_new_inst(FI_EA_SET, f_new_inst(FI_CONSTANT, empty), def);
- }
  static inline struct f_inst *
 -f_const_empty(enum f_type t)
 +f_implicit_roa_check(struct rtable_config *tab)
  {
 -  switch (t) {
 -    case T_PATH:
 -    case T_CLIST:
 -    case T_ECLIST:
 -    case T_LCLIST:
 -      return f_new_inst(FI_CONSTANT, (struct f_val) {
 -      .type = t,
 -      .val.ad = &null_adata,
 -      });
 -    default:
 -      return f_new_inst(FI_CONSTANT, (struct f_val) {});
 -  }
 +  const struct ea_class *def = ea_class_find("bgp_path");
 +  if (!def)
 +    cf_error("Fatal: Couldn't find BGP path attribute definition.");
 +
 +  struct f_static_attr fsa = f_new_static_attr(T_NET, SA_NET, 1);
 +
 +  return f_new_inst(FI_ROA_CHECK,
 +          f_new_inst(FI_RTA_GET, fsa),
 +          f_new_inst(FI_AS_PATH_LAST, f_new_inst(FI_EA_GET, def)),
 +          tab);
  }
  
  /*
@@@ -347,32 -351,35 +368,32 @@@ assert_assign(struct f_lval *lval, stru
  
  CF_DECLS
  
 -CF_KEYWORDS_EXCLUSIVE(IN)
  CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
        ACCEPT, REJECT, ERROR,
-       INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC,
+       INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC,
        SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
        IF, THEN, ELSE, CASE,
 -      FOR, DO,
 +      FOR, IN, DO,
        TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
-       FROM, GW, NET, MASK, PROTO, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS,
-       ROA_CHECK, ASN, SRC, DST,
-       IS_V4, IS_V6,
-       LEN, MAXLEN,
-       DATA, DATA1, DATA2,
 -      FROM, GW, NET, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS, ONLINK,
 -      PREFERENCE,
++      FROM, GW, NET, PROTO, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS,
+       ROA_CHECK,
        DEFINED,
        ADD, DELETE, RESET,
-       PREPEND, FIRST, LAST, LAST_NONAGGREGATED,
-       MIN, MAX,
+       PREPEND,
        EMPTY,
        FILTER, WHERE, EVAL, ATTRIBUTE,
        FROM_HEX,
 -      BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
 +      BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT, STACKS)
  
+ CF_METHODS(IS_V4, TYPE, IP, RD, LEN, MAXLEN, ASN, SRC, DST, MASK,
+       FIRST, LAST, LAST_NONAGGREGATED, DATA, DATA1, DATA2, MIN, MAX,
+       EMPTY, PREPEND, ADD, DELETE, FILTER)
  %nonassoc THEN
  %nonassoc ELSE
  
  %type <xp> cmds_int cmd_prep
- %type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail method_cmd method_term
+ %type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail term_dot_method method_name_cont
 -%type <fda> dynamic_attr
  %type <fsa> static_attr
  %type <f> filter where_filter
  %type <fl> filter_body function_body
@@@ -401,8 -402,8 +422,9 @@@ conf: filter_def 
  filter_def:
     FILTER symbol {
       $2 = cf_define_symbol(new_config, $2, SYM_FILTER, filter, NULL);
 +     cf_enter_filters();
       cf_push_scope( new_config, $2 );
+      this_function = NULL;
     } filter_body {
       struct filter *f = cfg_alloc(sizeof(struct filter));
       *f = (struct filter) { .sym = $2, .root = $4 };
@@@ -532,8 -525,8 +554,9 @@@ filter
       $$ = $1->filter;
     }
   | {
 +     cf_enter_filters();
       cf_push_scope(new_config, NULL);
+      this_function = NULL;
     } filter_body {
       struct filter *f = cfg_alloc(sizeof(struct filter));
       *f = (struct filter) { .root = $2 };
@@@ -562,12 -551,17 +585,18 @@@ function_body
   ;
  
  conf: function_def ;
+ maybe_type:
+    /* EMPTY */ { $$ = T_VOID; }
+  | type { $$ = $1; }
+  ;
  function_def:
-    FUNCTION symbol {
-      DBG( "Beginning of function %s\n", $2->name );
-      $2 = cf_define_symbol(new_config, $2, SYM_FUNCTION, function, NULL);
+    FUNCTION maybe_type symbol {
+      DBG( "Beginning of function %s\n", $3->name );
+      this_function = cf_define_symbol(new_config, $3, SYM_FUNCTION, function, NULL);
+ /*   if ($2 == T_VOID) cf_warn("Support for functions without explicit return type will be removed soon" ); */
 +     cf_enter_filters();
-      cf_push_scope(new_config, $2);
+      cf_push_scope(new_config, this_function);
     } function_args {
       /* Make dummy f_line for storing function prototype */
       struct f_line *dummy = cfg_allocz(sizeof(struct f_line));
         dummy->args++;
       }
     } function_body {
-      $6->args = $2->function->args;
-      $6->arg_list = $2->function->arg_list;
-      $2->function = $6;
+      $7->args = this_function->function->args;
+      $7->arg_list = this_function->function->arg_list;
+      $7->return_type = this_function->function->return_type;
+      $3->function = $7;
       cf_pop_scope(new_config);
 +     cf_exit_filters();
     }
   ;
  
@@@ -866,36 -865,23 +898,21 @@@ static_attr
   | IFNAME  { $$ = f_new_static_attr(T_STRING,     SA_IFNAME,  0); }
   | IFINDEX { $$ = f_new_static_attr(T_INT,        SA_IFINDEX, 1); }
   | WEIGHT  { $$ = f_new_static_attr(T_INT,        SA_WEIGHT,  0); }
 - | PREFERENCE { $$ = f_new_static_attr(T_INT,   SA_PREF,      0); }
   | GW_MPLS { $$ = f_new_static_attr(T_INT,        SA_GW_MPLS, 0); }
 - | ONLINK  { $$ = f_new_static_attr(T_BOOL,       SA_ONLINK,  0); }
   ;
  
- method_term:
-    IS_V4 { $$ = f_new_inst(FI_IS_V4, FM.object); }
-  | TYPE { $$ = f_new_inst(FI_TYPE, FM.object); }
-  | IP { $$ = f_new_inst(FI_IP, FM.object); }
-  | RD { $$ = f_new_inst(FI_ROUTE_DISTINGUISHER, FM.object); }
-  | LEN { $$ = f_new_inst(FI_LENGTH, FM.object); }
-  | MAXLEN { $$ = f_new_inst(FI_ROA_MAXLEN, FM.object); }
-  | ASN { $$ = f_new_inst(FI_ASN, FM.object); }
-  | SRC { $$ = f_new_inst(FI_NET_SRC, FM.object); }
-  | DST { $$ = f_new_inst(FI_NET_DST, FM.object); }
-  | MASK '(' term ')' { $$ = f_new_inst(FI_IP_MASK, FM.object, $3); }
-  | FIRST { $$ = f_new_inst(FI_AS_PATH_FIRST, FM.object); }
-  | LAST  { $$ = f_new_inst(FI_AS_PATH_LAST, FM.object); }
-  | LAST_NONAGGREGATED  { $$ = f_new_inst(FI_AS_PATH_LAST_NAG, FM.object); }
-  | DATA { $$ = f_new_inst(FI_PAIR_DATA, FM.object); }
-  | DATA1 { $$ = f_new_inst(FI_LC_DATA1, FM.object); }
-  | DATA2 { $$ = f_new_inst(FI_LC_DATA2, FM.object); }
-  | MIN  { $$ = f_new_inst(FI_MIN, FM.object); }
-  | MAX  { $$ = f_new_inst(FI_MAX, FM.object); }
-  ;
- method_cmd:
-    EMPTY              { $$ = f_new_inst(FI_CONSTANT, f_get_empty(FM.object->i_FI_EA_GET.da->type)); }
-  | PREPEND '(' term ')'       { $$ = f_new_inst(FI_PATH_PREPEND, FM.object, $3 ); }
-  | ADD '(' term ')'   { $$ = f_new_inst(FI_CLIST_ADD, FM.object, $3 ); }
-  | DELETE '(' term ')'        { $$ = f_new_inst(FI_CLIST_DEL, FM.object, $3 ); }
-  | FILTER '(' term ')'        { $$ = f_new_inst(FI_CLIST_FILTER, FM.object, $3 ); }
+ term_dot_method: term '.' { f_method_call_start($1); } method_name_cont { $$ = $4; };
+ method_name_cont:
+    CF_SYM_METHOD_BARE {
+      $$ = $1->method->new_inst(FM.object, NULL);
+      f_method_call_end();
+    }
+  | CF_SYM_METHOD_ARGS {
+      f_method_call_args();
+    } '(' var_list ')' {
+      $$ = $1->method->new_inst(FM.object, $4);
+      f_method_call_end();
+    }
   ;
  
  term:
  
   | static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
  
-  | term '.' {
-      f_push_method_scope($1);
-    } method_term {
-      f_pop_method_scope();
-      $$ = $4;
-    }
 - | dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
 -
+  | term_dot_method
  
 - | '+' EMPTY '+' { $$ = f_const_empty(T_PATH); }
 - | '-' EMPTY '-' { $$ = f_const_empty(T_CLIST); }
 - | '-' '-' EMPTY '-' '-' { $$ = f_const_empty(T_ECLIST); }
 - | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_const_empty(T_LCLIST); }
 + | '+' EMPTY '+' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_PATH)); }
 + | '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_CLIST)); }
 + | '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_ECLIST)); }
 + | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_LCLIST)); }
   | PREPEND '(' term ',' term ')' { $$ = f_new_inst(FI_PATH_PREPEND, $3, $5); }
-  | ADD '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_ADD, $3, $5); }
-  | DELETE '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_DEL, $3, $5); }
-  | FILTER '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); }
+  | ADD '(' term ',' term ')' {
+      switch ($3->type) {
+        case T_CLIST: $$ = f_new_inst(FI_CLIST_ADD, $3, $5); break;
+        case T_ECLIST: $$ = f_new_inst(FI_ECLIST_ADD, $3, $5); break;
+        case T_LCLIST: $$ = f_new_inst(FI_LCLIST_ADD, $3, $5); break;
+        default: cf_error("Can't add to type %s", f_type_name($3->type));
+      }
+      cf_warn("add(x,y) function is deprecated, please use x.add(y)");
+    }
+  | DELETE '(' term ',' term ')' {
+      switch ($3->type) {
+        case T_PATH: $$ = f_new_inst(FI_PATH_DEL, $3, $5); break;
+        case T_CLIST: $$ = f_new_inst(FI_CLIST_DEL, $3, $5); break;
+        case T_ECLIST: $$ = f_new_inst(FI_ECLIST_DEL, $3, $5); break;
+        case T_LCLIST: $$ = f_new_inst(FI_LCLIST_DEL, $3, $5); break;
+        default: cf_error("Can't delete from type %s", f_type_name($3->type));
+      }
+      cf_warn("delete(x,y) function is deprecated, please use x.delete(y)");
+    }
+  | FILTER '(' term ',' term ')' {
+      switch ($3->type) {
+        case T_PATH: $$ = f_new_inst(FI_PATH_FILTER, $3, $5); break;
+        case T_CLIST: $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); break;
+        case T_ECLIST: $$ = f_new_inst(FI_ECLIST_FILTER, $3, $5); break;
+        case T_LCLIST: $$ = f_new_inst(FI_LCLIST_FILTER, $3, $5); break;
+        default: cf_error("Can't filter type %s", f_type_name($3->type));
+      }
+      cf_warn("filter(x,y) function is deprecated, please use x.filter(y)");
+    }
  
 - | ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); }
 - | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); }
 + | ROA_CHECK '(' rtable ')' { $$ = f_implicit_roa_check($3); }
 + | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK, $5, $7, $3); }
  
   | FORMAT '(' term ')' {  $$ = f_new_inst(FI_FORMAT, $3); }
  
     }
   | RETURN term ';' {
       DBG( "Ook, we'll return the value\n" );
+      if (!this_function)
+        cf_error("Can't return from a non-function, use accept or reject instead.");
+      if (this_function->function->return_type == T_VOID)
+      {
+        if ($2->type != T_VOID)
+        cf_warn("Inferring function %s return type from its return value: %s", this_function->name, f_type_name($2->type));
+        ((struct f_line *) this_function->function)->return_type = $2->type;
+      }
+      else if (this_function->function->return_type != $2->type)
+        cf_error("Can't return type %s from function %s, expected %s",
+               f_type_name($2->type), this_function->name, f_type_name(this_function->function->return_type));
       $$ = f_new_inst(FI_RETURN, $2);
     }
 - | dynamic_attr '=' term ';' {
 -     $$ = f_new_inst(FI_EA_SET, $3, $1);
 -   }
   | static_attr '=' term ';' {
       if ($1.readonly)
         cf_error( "This static attribute is read-only.");
diff --cc filter/data.c
index 68f834a20ed5a3d6189ae634741fb86bfcbeffad,581150a2c51262e80055131b3a5ba141778d0311..d700f7d2b4321a7b34c7890dd9a192e542bf7ce5
@@@ -612,76 -621,3 +612,75 @@@ val_dump(const struct f_val *v) 
    val_format(v, &b);
    return val_dump_buffer;
  }
 +
 +struct f_val *
 +lp_val_copy(struct linpool *lp, const struct f_val *v)
 +{
 +  switch (v->type)
 +  {
 +    case T_VOID:
 +    case T_BOOL:
 +    case T_INT:
 +    case T_IP:
 +    case T_PAIR:
 +    case T_QUAD:
 +    case T_EC:
 +    case T_LC:
 +    case T_RD:
 +    case T_ENUM:
 +    case T_PATH_MASK_ITEM:
 +      /* These aren't embedded but there is no need to copy them */
 +    case T_SET:
 +    case T_PREFIX_SET:
 +    case T_PATH_MASK:
 +    case T_IFACE:
 +      {
 +      struct f_val *out = lp_alloc(lp, sizeof(*out));
 +      *out = *v;
 +      return out;
 +      }
 +
 +    case T_NET:
 +      {
 +      struct {
 +        struct f_val val;
 +        net_addr net[0];
 +      } *out = lp_alloc(lp, sizeof(*out) + v->val.net->length);
 +      out->val = *v;
 +      out->val.val.net = out->net;
 +      net_copy(out->net, v->val.net);
 +      return &out->val;
 +      }
 +
 +    case T_STRING:
 +      {
 +      uint len = strlen(v->val.s);
 +      struct {
 +        struct f_val val;
 +        char buf[0];
 +      } *out = lp_alloc(lp, sizeof(*out) + len + 1);
 +      out->val = *v;
 +      out->val.val.s = out->buf;
 +      memcpy(out->buf, v->val.s, len+1);
 +      return &out->val;
 +      }
 +
 +    case T_PATH:
 +    case T_CLIST:
 +    case T_ECLIST:
 +    case T_LCLIST:
 +      {
 +      struct {
 +        struct f_val val;
 +        struct adata ad;
 +      } *out = lp_alloc(lp, sizeof(*out) + v->val.ad->length);
 +      out->val = *v;
 +      out->val.val.ad = &out->ad;
 +      memcpy(&out->ad, v->val.ad, v->val.ad->length);
 +      return &out->val;
 +      }
 +
 +    default:
 +      bug("Unknown type in value copy: %d", v->type);
 +  }
 +}
diff --cc filter/data.h
index 462acf6145e796f963242e7fd164a2014c08bb97,baa7114c448f299009287c241c7b0e99c1a433fe..b668ead05b9e868a0b19b297516dce28f684dd3a
  #define _BIRD_FILTER_DATA_H_
  
  #include "nest/bird.h"
 -
 -/* Type numbers must be in 0..0xff range */
 -#define T_MASK 0xff
 -
 -/* Internal types */
 -enum f_type {
 -/* Nothing. Simply nothing. */
 -  T_VOID = 0,
 -
 -/* User visible types, which fit in int */
 -  T_INT = 0x10,
 -  T_BOOL = 0x11,
 -  T_PAIR = 0x12,  /*  Notice that pair is stored as integer: first << 16 | second */
 -  T_QUAD = 0x13,
 -
 -/* Put enumerational types in 0x30..0x3f range */
 -  T_ENUM_LO = 0x30,
 -  T_ENUM_HI = 0x3f,
 -
 -  T_ENUM_RTS = 0x30,
 -  T_ENUM_BGP_ORIGIN = 0x31,
 -  T_ENUM_SCOPE = 0x32,
 -  T_ENUM_RTC = 0x33,
 -  T_ENUM_RTD = 0x34,
 -  T_ENUM_ROA = 0x35,
 -  T_ENUM_NETTYPE = 0x36,
 -  T_ENUM_RA_PREFERENCE = 0x37,
 -  T_ENUM_AF = 0x38,
 -
 -/* new enums go here */
 -  T_ENUM_EMPTY = 0x3f,        /* Special hack for atomic_aggr */
 -
 -#define T_ENUM T_ENUM_LO ... T_ENUM_HI
 -
 -/* Bigger ones */
 -  T_IP = 0x20,
 -  T_NET = 0x21,
 -  T_STRING = 0x22,
 -  T_PATH_MASK = 0x23, /* mask for BGP path */
 -  T_PATH = 0x24,              /* BGP path */
 -  T_CLIST = 0x25,             /* Community list */
 -  T_EC = 0x26,                /* Extended community value, u64 */
 -  T_ECLIST = 0x27,            /* Extended community list */
 -  T_LC = 0x28,                /* Large community value, lcomm */
 -  T_LCLIST = 0x29,            /* Large community list */
 -  T_RD = 0x2a,                /* Route distinguisher for VPN addresses */
 -  T_PATH_MASK_ITEM = 0x2b,    /* Path mask item for path mask constructors */
 -  T_BYTESTRING = 0x2c,
 -
 -  T_SET = 0x80,
 -  T_PREFIX_SET = 0x81,
 -} PACKED;
 +#include "lib/type.h"
  
+ struct f_method {
+   struct symbol *sym;
+   struct f_inst *(*new_inst)(struct f_inst *obj, struct f_inst *args);
+   uint arg_num;
+ };
  /* Filter value; size of this affects filter memory consumption */
  struct f_val {
 -  enum f_type type;   /* T_*  */
 -  union {
 -    uint i;
 -    u64 ec;
 -    lcomm lc;
 -    ip_addr ip;
 -    const net_addr *net;
 -    const char *s;
 -    const struct bytestring *bs;
 -    const struct f_tree *t;
 -    const struct f_trie *ti;
 -    const struct adata *ad;
 -    const struct f_path_mask *path_mask;
 -    struct f_path_mask_item pmi;
 -  } val;
 +  btype type; /* T_*  */
 +  union bval_long val;
  };
  
 -/* Dynamic attribute definition (eattrs) */
 -struct f_dynamic_attr {
 -  u8 type;            /* EA type (EAF_*) */
 -  u8 bit;             /* For bitfield accessors */
 -  enum f_type f_type; /* Filter type */
 -  uint ea_code;               /* EA code */
 -};
 +#define fputip(a)   ({ ip_addr *ax = falloc(sizeof(*ax)); *ax = (a); ax; })
  
  enum f_sa_code {
 -  SA_FROM = 1,
 -  SA_GW,
 +  SA_GW = 1,
    SA_NET,
    SA_PROTO,
 -  SA_SOURCE,
 -  SA_SCOPE,
    SA_DEST,
    SA_IFNAME,
    SA_IFINDEX,
@@@ -209,9 -282,9 +215,9 @@@ trie_match_next_longest_ip6(net_addr_ip
  
  #define F_CMP_ERROR 999
  
 -const char *f_type_name(enum f_type t);
 -enum f_type f_type_element_type(enum f_type t);
 -struct sym_scope *f_type_method_scope(enum f_type t);
 +const char *f_type_name(btype t);
 +enum btype f_type_element_type(btype t);
++struct sym_scope *f_type_method_scope(btype t);
  
  int val_same(const struct f_val *v1, const struct f_val *v2);
  int val_compare(const struct f_val *v1, const struct f_val *v2);
diff --cc filter/decl.m4
index ed7630fc99a0b53dcddca6478aa74733f0070ee6,46429f6aea90bec133e2b1fa46e434637baa06f5..8900568754f6da3880116d56037a5f61baf2f61d
@@@ -264,9 -275,17 +276,17 @@@ FID_INTERPRET_BODY()'
  m4_define(SYMBOL, `FID_MEMBER(struct symbol *, sym, [[strcmp(f1->sym->name, f2->sym->name) || (f1->sym->class != f2->sym->class)]], "symbol %s", item->sym->name)')
  m4_define(RTC, `FID_MEMBER(struct rtable_config *, rtc, [[strcmp(f1->rtc->name, f2->rtc->name)]], "route table %s", item->rtc->name)')
  m4_define(STATIC_ATTR, `FID_MEMBER(struct f_static_attr, sa, f1->sa.sa_code != f2->sa.sa_code,,)')
 -m4_define(DYNAMIC_ATTR, `FID_MEMBER(struct f_dynamic_attr, da, f1->da.ea_code != f2->da.ea_code,,)')
 +m4_define(DYNAMIC_ATTR, `FID_MEMBER(const struct ea_class *, da, f1->da != f2->da,,)')
  m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access"); } while (0)]],NEVER_CONSTANT())')
  
+ #     Method constructor block
+ m4_define(METHOD_CONSTRUCTOR, `m4_dnl
+ FID_NEW_METHOD()m4_dnl
+     if (args) cf_error("Too many arguments");
+ m4_define([[INST_IS_METHOD]])
+ m4_define([[INST_METHOD_NAME]],$1)
+ FID_INTERPRET_BODY()')
  #     2) Code wrapping
  #     The code produced in 1xx temporary diversions is a raw code without
  #     any auxiliary commands and syntactical structures around. When the
@@@ -362,6 -385,35 +386,33 @@@ m4_undivert(102)m4_dn
    }
  ]])
  
 -  sym = cf_new_symbol(&f_type_method_scopes[INST_METHOD_OBJECT_TYPE],
 -                    global_root_scope_pool, global_root_scope_linpool,
 -                    INST_METHOD_NAME);
+ m4_ifdef([[INST_IS_METHOD]],m4_dnl
+ FID_METHOD()m4_dnl
+ [[struct f_inst * NONNULL(1)
+ f_new_method_]]INST_NAME()[[(struct f_inst *obj, struct f_inst *args)
+   {
+     /* Unwind the arguments (INST_METHOD_NUM_ARGS) */
+     m4_undivert(111)m4_dnl
+     return f_new_inst(INST_NAME, obj
+ m4_undivert(112)
+     );
+   }
+ FID_METHOD_SCOPE_INIT()m4_dnl
+   [INST_METHOD_OBJECT_TYPE] = {},
+ FID_METHOD_REGISTER()m4_dnl
++  sym = cf_root_symbol(INST_METHOD_NAME, &f_type_method_scopes[INST_METHOD_OBJECT_TYPE]);
+   sym->class = SYM_METHOD;
+   sym->method = method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method));
+   *method = (struct f_method) {
+     .sym = sym,
+     .new_inst = f_new_method_]]INST_NAME()[[,
+     .arg_num = INST_METHOD_NUM_ARGS,
+   };
+ ]])m4_dnl
  FID_DUMP_CALLER()m4_dnl                        Case in another big switch used in instruction dumping (debug)
  case INST_NAME(): f_dump_line_item_]]INST_NAME()[[(item, indent + 1); break;
  
@@@ -538,6 -592,37 +591,37 @@@ FID_WR_PUT(3
  #undef v3
  #undef vv
  
 -struct sym_scope *f_type_method_scope(enum f_type t)
+ /* Method constructor wrappers */
+ FID_WR_PUT(11)
+ #if defined(__GNUC__) && __GNUC__ >= 6
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Woverride-init"
+ #endif
+ static struct sym_scope f_type_method_scopes[] = {
+ FID_WR_PUT(12)
+ };
+ #if defined(__GNUC__) && __GNUC__ >= 6
+ #pragma GCC diagnostic pop
+ #endif
++struct sym_scope *f_type_method_scope(enum btype t)
+ {
+   return (t < ARRAY_SIZE(f_type_method_scopes)) ? &f_type_method_scopes[t] : NULL;
+ }
+ void f_type_methods_register(void)
+ {
+   struct symbol *sym;
+   struct f_method *method;
+ FID_WR_PUT(13)
+   for (uint i = 0; i < ARRAY_SIZE(f_type_method_scopes); i++)
+     f_type_method_scopes[i].readonly = 1;
+ }
  /* Line dumpers */
  #define INDENT (((const char *) f_dump_line_indent_str) + sizeof(f_dump_line_indent_str) - (indent) - 1)
  static const char f_dump_line_indent_str[] = "                                ";
diff --cc filter/f-inst.c
index de5314fc33649a851214d46b30bede5570878b7c,16d01e4d200189566af4b7d984389b2a35427268..8a00438b720e09ae0775b69835f29a983a1afa90
   *    m4_dnl    DYNAMIC_ATTR;                         dynamic attribute definition
   *    m4_dnl    RTC;                                  route table config
   *    m4_dnl    ACCESS_RTE;                           this instruction needs route
 - *    m4_dnl    ACCESS_EATTRS;                        this instruction needs extended attributes
   *
+  *    m4_dnl    METHOD_CONSTRUCTOR(name);             this instruction is in fact a method of the first argument's type; register it with the given name for that type
+  *
   *    m4_dnl    FID_MEMBER(                           custom instruction member
   *    m4_dnl      C type,                             for storage in structs
   *    m4_dnl      name,                               how the member is named
  
      /* New variable is always the last on stack */
      uint pos = curline.vbase + sym->offset;
--    fstk->vstk[pos] = (struct f_val) { };
++    fstk->vstk[pos] = f_get_empty(sym->class & 0xff);
      fstk->vcnt = pos + 1;
    }
  
    INST(FI_EA_UNSET, 0, 0) {
      DYNAMIC_ATTR;
      ACCESS_RTE;
 -    ACCESS_EATTRS;
  
 -    f_rta_cow(fs);
 -    ea_unset_attr(fs->eattrs, fs->pool, 1, da.ea_code);
 +    ea_unset_attr(&fs->rte->attrs, 1, da);
 +  }
 +
 +  INST(FI_DEFAULT, 2, 1) {
 +    ARG_ANY(1);
 +    ARG_ANY(2);
 +    RESULT_TYPE(f_type_element_type(v2.type));
 +
 +    log(L_INFO "Type of arg 1 is: %d", v1.type);
 +
 +    if (v1.type == T_VOID)
 +      RESULT_VAL(v2);
 +    else
 +      RESULT_VAL(v1);
    }
  
-   INST(FI_LENGTH, 1, 1) {     /* Get length of */
-     ARG_ANY(1);
-     switch(v1.type) {
-     case T_NET:    RESULT(T_INT, i, net_pxlen(v1.val.net)); break;
-     case T_PATH:   RESULT(T_INT, i, as_path_getlen(v1.val.ad)); break;
-     case T_CLIST:  RESULT(T_INT, i, int_set_get_size(v1.val.ad)); break;
-     case T_ECLIST: RESULT(T_INT, i, ec_set_get_size(v1.val.ad)); break;
-     case T_LCLIST: RESULT(T_INT, i, lc_set_get_size(v1.val.ad)); break;
-     default: runtime( "Prefix, path, clist or eclist expected" );
-     }
+   INST(FI_NET_LENGTH, 1, 1) { /* Get length of */
+     ARG(1, T_NET);
+     METHOD_CONSTRUCTOR("len");
+     RESULT(T_INT, i, net_pxlen(v1.val.net));
+   }
+   INST(FI_PATH_LENGTH, 1, 1) {        /* Get length of */
+     ARG(1, T_PATH);
+     METHOD_CONSTRUCTOR("len");
+     RESULT(T_INT, i, as_path_getlen(v1.val.ad));
+   }
+   INST(FI_CLIST_LENGTH, 1, 1) {       /* Get length of */
+     ARG(1, T_CLIST);
+     METHOD_CONSTRUCTOR("len");
+     RESULT(T_INT, i, int_set_get_size(v1.val.ad));
+   }
+   INST(FI_ECLIST_LENGTH, 1, 1) { /* Get length of */
+     ARG(1, T_ECLIST);
+     METHOD_CONSTRUCTOR("len");
+     RESULT(T_INT, i, ec_set_get_size(v1.val.ad));
+   }
+   INST(FI_LCLIST_LENGTH, 1, 1) {      /* Get length of */
+     ARG(1, T_LCLIST);
+     METHOD_CONSTRUCTOR("len");
+     RESULT(T_INT, i, lc_set_get_size(v1.val.ad));
    }
  
    INST(FI_NET_SRC, 1, 1) {    /* Get src prefix */
        struct f_val dummy;
  
        if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
-       RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
        else
        runtime("Can't filter pair");
-     }
+   }
  
-     else if (v1.type == T_ECLIST)
-     {
+   INST(FI_ECLIST_FILTER, 2, 1) {
+     ARG(1, T_ECLIST);
+     ARG_ANY(2);
+     METHOD_CONSTRUCTOR("filter");
        /* v2.val is either EC or EC-set */
        if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
-       RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
        else
        runtime("Can't filter ec");
-     }
+   }
  
-     else if (v1.type == T_LCLIST)
-     {
+   INST(FI_LCLIST_FILTER, 2, 1) {
+     ARG(1, T_LCLIST);
+     ARG_ANY(2);
+     METHOD_CONSTRUCTOR("filter");
        /* v2.val is either LC or LC-set */
        if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
-       RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+       RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
        else
        runtime("Can't filter lc");
-     }
-     else
-       runtime("Can't filter non-[e|l]clist");
    }
  
 -  INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */
 -    NEVER_CONSTANT;
 -    RTC(1);
 -    struct rtable *table = rtc->table;
 -    ACCESS_RTE;
 -    ACCESS_EATTRS;
 -    const net_addr *net = (*fs->rte)->net->n.addr;
 -
 -    /* We ignore temporary attributes, probably not a problem here */
 -    /* 0x02 is a value of BA_AS_PATH, we don't want to include BGP headers */
 -    eattr *e = ea_find(*fs->eattrs, EA_CODE(PROTOCOL_BGP, 0x02));
 -
 -    if (!e || ((e->type & EAF_TYPE_MASK) != EAF_TYPE_AS_PATH))
 -      runtime("Missing AS_PATH attribute");
 -
 -    u32 as = 0;
 -    as_path_get_last(e->u.ptr, &as);
 -
 -    if (!table)
 -      runtime("Missing ROA table");
 -
 -    if (table->addr_type != NET_ROA4 && table->addr_type != NET_ROA6)
 -      runtime("Table type must be either ROA4 or ROA6");
 -
 -    if (table->addr_type != (net->type == NET_IP4 ? NET_ROA4 : NET_ROA6))
 -      RESULT(T_ENUM_ROA, i, ROA_UNKNOWN); /* Prefix and table type mismatch */
 -    else
 -      RESULT(T_ENUM_ROA, i, [[ net_roa_check(table, net, as) ]]);
 -  }
 -
 -  INST(FI_ROA_CHECK_EXPLICIT, 2, 1) { /* ROA Check */
 +  INST(FI_ROA_CHECK, 2, 1) {  /* ROA Check */
      NEVER_CONSTANT;
      ARG(1, T_NET);
      ARG(2, T_INT);
diff --cc filter/f-inst.h
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 0c89ebd65a700556426e8e6f92f5a9d9dd2cd89d,53122aee3b98696cec871c2bc68f2cdf0b49d192..81558d0a2f666393ee11065135604d7450c04195
@@@ -173,46 -152,49 +173,46 @@@ log_rotate(struct log_channel *lc
   * in log(), so it should be written like *L_INFO.
   */
  void
 -log_commit(int class, buffer *buf)
 +log_commit(log_buffer *buf)
  {
 -  struct log_config *l;
 -
 -  if (buf->pos == buf->end)
 -    strcpy(buf->end - 100, " ... <too long>");
 -
 -  log_lock();
 -  WALK_LIST(l, *current_log_list)
 +  if (buf->buf.pos == buf->buf.end)
 +#define TOO_LONG " ... <too long>"
 +    memcpy(buf->buf.end - sizeof TOO_LONG, TOO_LONG, sizeof TOO_LONG);
 +#undef TOO_LONG
 +
 +  for (
 +      struct log_channel *l = atomic_load_explicit(&global_logs, memory_order_acquire);
 +      l;
 +      l = atomic_load_explicit(&l->next, memory_order_acquire)
 +      )
      {
 -      if (!(l->mask & (1 << class)))
 +      uint mask = atomic_load_explicit(&l->mask, memory_order_acquire);
 +      if (!(mask & (1 << buf->class)))
        continue;
 -      if (l->fh)
 +
 +      struct rfile *rf = atomic_load_explicit(&l->rf, memory_order_acquire);
 +      if (rf && buf->tm_pos)
        {
 -        if (l->terminal_flag)
 -          fputs("bird: ", l->fh);
 -        else
 +        *buf->buf.pos = '\n';
 +        byte *begin = l->terminal ? buf->buf.start : buf->tm_pos;
 +        off_t msg_len = buf->buf.pos - begin + 1;
 +        do {
-           if (rf_write(rf, buf->tm_pos, msg_len))
++          if (rf_write(rf, begin, msg_len))
 +            break;
 +
 +          log_lock();
 +          rf = atomic_load_explicit(&l->rf, memory_order_acquire);
-           if (rf_write(rf, buf->tm_pos, msg_len))
++          if (rf_write(rf, begin, msg_len))
            {
 -            byte tbuf[TM_DATETIME_BUFFER_SIZE];
 -            const char *fmt = config ? config->tf_log.fmt1 : "%F %T.%3f";
 -            if (!tm_format_real_time(tbuf, sizeof(tbuf), fmt, current_real_time()))
 -              strcpy(tbuf, "<error>");
 -
 -            if (l->limit)
 -            {
 -              off_t msg_len = strlen(tbuf) + strlen(class_names[class]) +
 -                (buf->pos - buf->start) + 5;
 -
 -              if (l->pos < 0)
 -                l->pos = log_size(l);
 -
 -              if (l->pos + msg_len > l->limit)
 -                if (log_rotate(l) < 0)
 -                  continue;
 +            log_unlock();
 +            break;
 +          }
  
 -              l->pos += msg_len;
 -            }
 +          log_rotate(l);
 +          log_unlock();
  
 -            fprintf(l->fh, "%s <%s> ", tbuf, class_names[class]);
 -          }
 -        fputs(buf->start, l->fh);
 -        fputc('\n', l->fh);
 -        fflush(l->fh);
 +          rf = atomic_load_explicit(&l->rf, memory_order_relaxed);
-         } while (!rf_write(rf, buf->tm_pos, msg_len));
++        } while (!rf_write(rf, begin, msg_len));
        }
  #ifdef HAVE_SYSLOG_H
        else
  
  int buffer_vprint(buffer *buf, const char *fmt, va_list args);
  
 +void
 +log_prepare(log_buffer *buf, int class)
 +{
 +  buf->buf.start = buf->buf.pos = buf->block;
 +  buf->buf.end = buf->block + sizeof buf->block;
 +
 +  int lf = atomic_load_explicit(&logging_flags, memory_order_acquire);
 +  if (lf & LOGGING_TO_TERMINAL)
 +    buffer_puts(&buf->buf, "bird: ");
 +
 +  if (lf & LOGGING_TO_FILE)
 +  {
 +    const char *fmt = config ? config->tf_log.fmt1 : "%F %T.%3f";
 +
 +    buf->tm_pos = buf->buf.pos;
 +    int t = tm_format_real_time(buf->buf.pos, buf->buf.end - buf->buf.pos, fmt, current_real_time());
 +    if (t)
 +      buf->buf.pos += t;
 +    else
 +      buffer_puts(&buf->buf, "<time format error>");
 +
 +    buffer_print(&buf->buf, " [%04x] <%s> ", THIS_THREAD_ID, class_names[class]);
 +  }
++  else
++    buf->tm_pos = NULL;
 +
 +  buf->msg_pos = buf->buf.pos;
 +  buf->class = class;
 +}
 +
  static void
  vlog(int class, const char *msg, va_list args)
  {