]> git.ipfire.org Git - thirdparty/bird.git/blobdiff - conf/conf.c
Merge branch 'master' into mq-filter-stack
[thirdparty/bird.git] / conf / conf.c
index 9375861fbf741ce8bffbb4a1511c44ca875ea914..b21d5213ad198a4347ce81a183cbeaf478880378 100644 (file)
  *
  * There can exist up to four different configurations at one time: an active
  * one (pointed to by @config), configuration we are just switching from
- * (@old_config), one queued for the next reconfiguration (@future_config;
- * if it's non-%NULL and the user wants to reconfigure once again, we just
- * free the previous queued config and replace it with the new one) and
- * finally a config being parsed (@new_config).
+ * (@old_config), one queued for the next reconfiguration (@future_config; if
+ * there is one and the user wants to reconfigure once again, we just free the
+ * previous queued config and replace it with the new one) and finally a config
+ * being parsed (@new_config). The stored @old_config is also used for undo
+ * reconfiguration, which works in a similar way. Reconfiguration could also
+ * have timeout (using @config_timer) and undo is automatically called if the
+ * new configuration is not confirmed later. The new config (@new_config) and
+ * associated linear pool (@cfg_mem) is non-NULL only during parsing.
  *
- * Loading of new configuration is very simple: just call config_alloc()
- * to get a new &config structure, then use config_parse() to parse a
- * configuration file and fill all fields of the structure
- * and finally ask the config manager to switch to the new
- * config by calling config_commit().
+ * Loading of new configuration is very simple: just call config_alloc() to get
+ * a new &config structure, then use config_parse() to parse a configuration
+ * file and fill all fields of the structure and finally ask the config manager
+ * to switch to the new config by calling config_commit().
  *
  * CLI commands are parsed in a very similar way -- there is also a stripped-down
  * &config structure associated with them and they are lex-ed and parsed by the
 #include "conf/conf.h"
 #include "filter/filter.h"
 
+
 static jmp_buf conf_jmpbuf;
 
-struct config *config, *new_config, *old_config, *future_config;
-static event *config_event;
-int shutting_down, future_type;
-bird_clock_t boot_time;
+struct config *config, *new_config;
+
+static struct config *old_config;      /* Old configuration */
+static struct config *future_config;   /* New config held here if recon requested during recon */
+static int old_cftype;                 /* Type of transition old_config -> config (RECONFIG_SOFT/HARD) */
+static int future_cftype;              /* Type of scheduled transition, may also be RECONFIG_UNDO */
+/* Note that when future_cftype is RECONFIG_UNDO, then future_config is NULL,
+   therefore proper check for future scheduled config checks future_cftype */
+
+static event *config_event;            /* Event for finalizing reconfiguration */
+static timer *config_timer;            /* Timer for scheduled configuration rollback */
+
+/* These are public just for cmd_show_status(), should not be accessed elsewhere */
+int shutting_down;                     /* Shutdown requested, do not accept new config changes */
+int configuring;                       /* Reconfiguration is running */
+int undo_available;                    /* Undo was not requested from last reconfiguration */
+/* Note that both shutting_down and undo_available are related to requests, not processing */
 
 /**
  * config_alloc - allocate a new configuration
@@ -69,21 +86,28 @@ bird_clock_t boot_time;
  * further use. Returns a pointer to the structure.
  */
 struct config *
-config_alloc(byte *name)
+config_alloc(const char *name)
 {
   pool *p = rp_new(&root_pool, "Config");
-  linpool *l = lp_new(p, 4080);
+  linpool *l = lp_new_default(p);
   struct config *c = lp_allocz(l, sizeof(struct config));
 
+  /* Duplication of name string in local linear pool */
+  uint nlen = strlen(name) + 1;
+  char *ndup = lp_allocu(l, nlen);
+  memcpy(ndup, name, nlen);
+
+  init_list(&c->tests);
+  init_list(&c->symbols);
   c->mrtdump_file = -1; /* Hack, this should be sysdep-specific */
   c->pool = p;
-  cfg_mem = c->mem = l;
-  c->file_name = cfg_strdup(name);
-  c->load_time = now;
-  c->tf_base.fmt1 = c->tf_log.fmt1 = "%d-%m-%Y %T";
+  c->mem = l;
+  c->file_name = ndup;
+  c->load_time = current_time();
+  c->tf_route = c->tf_proto = TM_ISO_SHORT_MS;
+  c->tf_base = c->tf_log = TM_ISO_LONG_MS;
+  c->gr_wait = DEFAULT_GR_WAIT;
 
-  if (!boot_time)
-    boot_time = now;
   return c;
 }
 
@@ -103,25 +127,33 @@ config_alloc(byte *name)
 int
 config_parse(struct config *c)
 {
+  int done = 0;
   DBG("Parsing configuration file `%s'\n", c->file_name);
   new_config = c;
   cfg_mem = c->mem;
   if (setjmp(conf_jmpbuf))
-    return 0;
+    goto cleanup;
+
   cf_lex_init(0, c);
   sysdep_preconfig(c);
   protos_preconfig(c);
   rt_preconfig(c);
-  roa_preconfig(c);
   cf_parse();
-  protos_postconfig(c);
+
   if (EMPTY_LIST(c->protos))
     cf_error("No protocol is specified in the config file");
-#ifdef IPV6
+
+  /*
   if (!c->router_id)
-    cf_error("Router ID must be configured manually on IPv6 routers");
-#endif
-  return 1;
+    cf_error("Router ID must be configured manually");
+  */
+
+  done = 1;
+
+cleanup:
+  new_config = NULL;
+  cfg_mem = NULL;
+  return done;
 }
 
 /**
@@ -134,14 +166,22 @@ config_parse(struct config *c)
 int
 cli_parse(struct config *c)
 {
+  int done = 0;
+  c->fallback = config;
   new_config = c;
-  c->sym_fallback = config->sym_hash;
   cfg_mem = c->mem;
   if (setjmp(conf_jmpbuf))
-    return 0;
+    goto cleanup;
+
   cf_lex_init(1, c);
   cf_parse();
-  return 1;
+  done = 1;
+
+cleanup:
+  c->fallback = NULL;
+  new_config = NULL;
+  cfg_mem = NULL;
+  return done;
 }
 
 /**
@@ -154,7 +194,8 @@ cli_parse(struct config *c)
 void
 config_free(struct config *c)
 {
-  rfree(c->pool);
+  if (c)
+    rfree(c->pool);
 }
 
 void
@@ -169,11 +210,8 @@ config_del_obstacle(struct config *c)
 {
   DBG("+++ deleting obstacle %d\n", c->obstacle_count);
   c->obstacle_count--;
-  if (!c->obstacle_count)
-    {
-      ASSERT(config_event);
-      ev_schedule(config_event);
-    }
+  if (!c->obstacle_count && (c != config))
+    ev_schedule(config_event);
 }
 
 static int
@@ -182,70 +220,84 @@ global_commit(struct config *new, struct config *old)
   if (!old)
     return 0;
 
-  if (!ipa_equal(old->listen_bgp_addr, new->listen_bgp_addr) ||
-      (old->listen_bgp_port != new->listen_bgp_port) ||
-      (old->listen_bgp_flags != new->listen_bgp_flags))
-    log(L_WARN "Reconfiguration of BGP listening socket not implemented, please restart BIRD.");
-
   if (!new->router_id)
-    new->router_id = old->router_id;
-  if (new->router_id != old->router_id)
-    return 1;
+    {
+      new->router_id = old->router_id;
+
+      if (new->router_id_from)
+       {
+         u32 id = if_choose_router_id(new->router_id_from, old->router_id);
+         if (!id)
+           log(L_WARN "Cannot determine router ID, using old one");
+         else
+           new->router_id = id;
+       }
+    }
+
   return 0;
 }
 
 static int
 config_do_commit(struct config *c, int type)
 {
-  int force_restart, nobs;
+  if (type == RECONFIG_UNDO)
+    {
+      c = old_config;
+      type = old_cftype;
+    }
+  else
+    config_free(old_config);
 
-  DBG("do_commit\n");
   old_config = config;
-  config = new_config = c;
+  old_cftype = type;
+  config = c;
+
+  configuring = 1;
+  if (old_config && !config->shutdown)
+    log(L_INFO "Reconfiguring");
+
   if (old_config)
     old_config->obstacle_count++;
 
+  DBG("filter_commit\n");
+  filter_commit(c, old_config);
   DBG("sysdep_commit\n");
-  force_restart = sysdep_commit(c, old_config);
+  int force_restart = sysdep_commit(c, old_config);
   DBG("global_commit\n");
   force_restart |= global_commit(c, old_config);
   DBG("rt_commit\n");
   rt_commit(c, old_config);
-  roa_commit(c, old_config);
   DBG("protos_commit\n");
   protos_commit(c, old_config, force_restart, type);
-  new_config = NULL;                   /* Just to be sure nobody uses that now */
+
+  int obs = 0;
   if (old_config)
-    nobs = --old_config->obstacle_count;
-  else
-    nobs = 0;
-  DBG("do_commit finished with %d obstacles remaining\n", nobs);
-  return !nobs;
+    obs = --old_config->obstacle_count;
+
+  DBG("do_commit finished with %d obstacles remaining\n", obs);
+  return !obs;
 }
 
 static void
 config_done(void *unused UNUSED)
 {
-  struct config *c;
+  if (config->shutdown)
+    sysdep_shutdown_done();
+
+  configuring = 0;
+  if (old_config)
+    log(L_INFO "Reconfigured");
 
-  DBG("config_done\n");
-  for(;;)
+  if (future_cftype)
     {
-      if (config->shutdown)
-       sysdep_shutdown_done();
-      log(L_INFO "Reconfigured");
-      if (old_config)
-       {
-         config_free(old_config);
-         old_config = NULL;
-       }
-      if (!future_config)
-       break;
-      c = future_config;
+      int type = future_cftype;
+      struct config *conf = future_config;
+      future_cftype = RECONFIG_NONE;
       future_config = NULL;
+
       log(L_INFO "Reconfiguring to queued configuration");
-      if (!config_do_commit(c, future_type))
-       break;
+      if (config_do_commit(conf, type))
+       config_done(NULL);
     }
 }
 
@@ -253,6 +305,7 @@ config_done(void *unused UNUSED)
  * config_commit - commit a configuration
  * @c: new configuration
  * @type: type of reconfiguration (RECONFIG_SOFT or RECONFIG_HARD)
+ * @timeout: timeout for undo (in seconds; or 0 for no timeout)
  *
  * When a configuration is parsed and prepared for use, the
  * config_commit() function starts the process of reconfiguration.
@@ -265,6 +318,10 @@ config_done(void *unused UNUSED)
  * using config_del_obstacle(), the old configuration is freed and
  * everything runs according to the new one.
  *
+ * When @timeout is nonzero, the undo timer is activated with given
+ * timeout. The timer is deactivated when config_commit(),
+ * config_confirm() or config_undo() is called.
+ *
  * Result: %CONF_DONE if the configuration has been accepted immediately,
  * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED
  * if it's been queued due to another reconfiguration being in progress now
@@ -272,49 +329,165 @@ config_done(void *unused UNUSED)
  * are accepted.
  */
 int
-config_commit(struct config *c, int type)
+config_commit(struct config *c, int type, uint timeout)
 {
-  if (!config)                         /* First-time configuration */
+  if (shutting_down)
     {
-      config_do_commit(c, RECONFIG_HARD);
-      return CONF_DONE;
+      config_free(c);
+      return CONF_SHUTDOWN;
     }
-  if (old_config)                      /* Reconfiguration already in progress */
+
+  undo_available = 1;
+  if (timeout)
+    tm_start(config_timer, timeout S);
+  else
+    tm_stop(config_timer);
+
+  if (configuring)
     {
-      if (shutting_down == 2)
-       {
-         log(L_INFO "New configuration discarded due to shutdown");
-         config_free(c);
-         return CONF_SHUTDOWN;
-       }
-      if (future_config)
+      if (future_cftype)
        {
          log(L_INFO "Queueing new configuration, ignoring the one already queued");
          config_free(future_config);
        }
       else
-       log(L_INFO "Queued new configuration");
+       log(L_INFO "Queueing new configuration");
+
+      future_cftype = type;
       future_config = c;
-      future_type = type;
       return CONF_QUEUED;
     }
 
-  if (!shutting_down)
-    log(L_INFO "Reconfiguring");
-
   if (config_do_commit(c, type))
     {
       config_done(NULL);
       return CONF_DONE;
     }
-  if (!config_event)
+  return CONF_PROGRESS;
+}
+
+/**
+ * config_confirm - confirm a commited configuration
+ *
+ * When the undo timer is activated by config_commit() with nonzero timeout,
+ * this function can be used to deactivate it and therefore confirm
+ * the current configuration.
+ *
+ * Result: %CONF_CONFIRM when the current configuration is confirmed,
+ * %CONF_NONE when there is nothing to confirm (i.e. undo timer is not active).
+ */
+int
+config_confirm(void)
+{
+  if (config_timer->expires == 0)
+    return CONF_NOTHING;
+
+  tm_stop(config_timer);
+
+  return CONF_CONFIRM;
+}
+
+/**
+ * config_undo - undo a configuration
+ *
+ * Function config_undo() can be used to change the current
+ * configuration back to stored %old_config. If no reconfiguration is
+ * running, this stored configuration is commited in the same way as a
+ * new configuration in config_commit(). If there is already a
+ * reconfiguration in progress and no next reconfiguration is
+ * scheduled, then the undo is scheduled for later processing as
+ * usual, but if another reconfiguration is already scheduled, then
+ * such reconfiguration is removed instead (i.e. undo is applied on
+ * the last commit that scheduled it).
+ *
+ * Result: %CONF_DONE if the configuration has been accepted immediately,
+ * %CONF_PROGRESS if it will take some time to switch to it, %CONF_QUEUED
+ * if it's been queued due to another reconfiguration being in progress now,
+ * %CONF_UNQUEUED if a scheduled reconfiguration is removed, %CONF_NOTHING
+ * if there is no relevant configuration to undo (the previous config request
+ * was config_undo() too)  or %CONF_SHUTDOWN if BIRD is in shutdown mode and
+ * no new configuration changes  are accepted.
+ */
+int
+config_undo(void)
+{
+  if (shutting_down)
+    return CONF_SHUTDOWN;
+
+  if (!undo_available || !old_config)
+    return CONF_NOTHING;
+
+  undo_available = 0;
+  tm_stop(config_timer);
+
+  if (configuring)
     {
-      config_event = ev_new(&root_pool);
-      config_event->hook = config_done;
+      if (future_cftype)
+       {
+         config_free(future_config);
+         future_config = NULL;
+
+         log(L_INFO "Removing queued configuration");
+         future_cftype = RECONFIG_NONE;
+         return CONF_UNQUEUED;
+       }
+      else
+       {
+         log(L_INFO "Queueing undo configuration");
+         future_cftype = RECONFIG_UNDO;
+         return CONF_QUEUED;
+       }
+    }
+
+  if (config_do_commit(NULL, RECONFIG_UNDO))
+    {
+      config_done(NULL);
+      return CONF_DONE;
     }
   return CONF_PROGRESS;
 }
 
+int
+config_status(void)
+{
+  if (shutting_down)
+    return CONF_SHUTDOWN;
+
+  if (configuring)
+    return future_cftype ? CONF_QUEUED : CONF_PROGRESS;
+
+  return CONF_DONE;
+}
+
+btime
+config_timer_status(void)
+{
+  return tm_active(config_timer) ? tm_remains(config_timer) : -1;
+}
+
+extern void cmd_reconfig_undo_notify(void);
+
+static void
+config_timeout(timer *t UNUSED)
+{
+  log(L_INFO "Config timeout expired, starting undo");
+  cmd_reconfig_undo_notify();
+
+  int r = config_undo();
+  if (r < 0)
+    log(L_ERR "Undo request failed");
+}
+
+void
+config_init(void)
+{
+  config_event = ev_new(&root_pool);
+  config_event->hook = config_done;
+
+  config_timer = tm_new(&root_pool);
+  config_timer->hook = config_timeout;
+}
+
 /**
  * order_shutdown - order BIRD shutdown
  *
@@ -322,21 +495,27 @@ config_commit(struct config *c, int type)
  * for switching to an empty configuration.
  */
 void
-order_shutdown(void)
+order_shutdown(int gr)
 {
   struct config *c;
 
   if (shutting_down)
     return;
-  log(L_INFO "Shutting down");
+
+  if (!gr)
+    log(L_INFO "Shutting down");
+  else
+    log(L_INFO "Shutting down for graceful restart");
+
   c = lp_alloc(config->mem, sizeof(struct config));
   memcpy(c, config, sizeof(struct config));
   init_list(&c->protos);
   init_list(&c->tables);
   c->shutdown = 1;
+  c->gr_down = gr;
+
+  config_commit(c, RECONFIG_HARD, 0);
   shutting_down = 1;
-  config_commit(c, RECONFIG_HARD);
-  shutting_down = 2;
 }
 
 /**
@@ -348,7 +527,7 @@ order_shutdown(void)
  * error in the configuration.
  */
 void
-cf_error(char *msg, ...)
+cf_error(const char *msg, ...)
 {
   char buf[1024];
   va_list args;
@@ -356,9 +535,12 @@ cf_error(char *msg, ...)
   va_start(args, msg);
   if (bvsnprintf(buf, sizeof(buf), msg, args) < 0)
     strcpy(buf, "<bug: error message too long>");
+  va_end(args);
   new_config->err_msg = cfg_strdup(buf);
   new_config->err_lino = ifs->lino;
+  new_config->err_chno = ifs->chno - ifs->toklen + 1;
   new_config->err_file_name = ifs->file_name;
+  cf_lex_unwind();
   longjmp(conf_jmpbuf, 1);
 }
 
@@ -372,7 +554,7 @@ cf_error(char *msg, ...)
  * and we want to preserve it for further use.
  */
 char *
-cfg_strdup(char *c)
+cfg_strdup(const char *c)
 {
   int l = strlen(c) + 1;
   char *z = cfg_allocu(l);