0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-11-15 07:38:43 +00:00
bird/sysdep/unix/conf.c
Maria Jan Matejka f93315c417 Config: Make the parser and lexer reentrant.
This is part of the multithreading journey. The parser and lexer were
using loads of global variables and all of these are now packed into
struct cf_context and others.

Note that the config API has changed:

* cfg_alloc[zu]?(size) is now cf_alloc[zu]?(ctx, size)
* cf_error(msg, ...) is now cf_error(ctx, msg, ...)
* config_parse() and cli_parse() are now called differently
* there is a brand new CF_CTX section in *.Y files which participates
  in struct cf_context construction
2018-09-14 14:44:45 +02:00

424 lines
9.6 KiB
C

/*
* BIRD Internet Routing Daemon -- Unix Config Reader
*
* (c) 1998--2000 Martin Mares <mj@ucw.cz>
* (c) 2018 Maria Matejka <mq@jmq.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "nest/bird.h"
#include "conf/conf.h"
#include "conf/parser.h"
#ifdef PATH_IPROUTE_DIR
static inline void
add_num_const(struct cf_context *ctx, char *name, int val)
{
struct symbol *s = cf_get_symbol(ctx, name);
s->class = SYM_CONSTANT | T_INT;
s->def = cfg_allocz(sizeof(struct f_val));
SYM_TYPE(s) = T_INT;
SYM_VAL(s).i = val;
}
/* the code of read_iproute_table() is based on
rtnl_tab_initialize() from iproute2 package */
static void
read_iproute_table(struct cf_context *ctx, char *file, char *prefix, int max)
{
char buf[512], namebuf[512];
char *name;
int val;
FILE *fp;
strcpy(namebuf, prefix);
name = namebuf + strlen(prefix);
fp = fopen(file, "r");
if (!fp)
return;
while (fgets(buf, sizeof(buf), fp))
{
char *p = buf;
while (*p == ' ' || *p == '\t')
p++;
if (*p == '#' || *p == '\n' || *p == 0)
continue;
if (sscanf(p, "0x%x %s\n", &val, name) != 2 &&
sscanf(p, "0x%x %s #", &val, name) != 2 &&
sscanf(p, "%d %s\n", &val, name) != 2 &&
sscanf(p, "%d %s #", &val, name) != 2)
continue;
if (val < 0 || val > max)
continue;
for(p = name; *p; p++)
if ((*p < 'a' || *p > 'z') && (*p < '0' || *p > '9') && (*p != '_'))
*p = '_';
add_num_const(ctx, namebuf, val);
}
fclose(fp);
}
#endif // PATH_IPROUTE_DIR
char *config_name = PATH_CONFIG_FILE;
void
sysdep_preconfig(struct cf_context *ctx)
{
init_list(&ctx->new_config->logfiles);
ctx->new_config->latency_limit = UNIX_DEFAULT_LATENCY_LIMIT;
ctx->new_config->watchdog_warning = UNIX_DEFAULT_WATCHDOG_WARNING;
#ifdef PATH_IPROUTE_DIR
read_iproute_table(ctx, PATH_IPROUTE_DIR "/rt_protos", "ipp_", 256);
read_iproute_table(ctx, PATH_IPROUTE_DIR "/rt_realms", "ipr_", 256);
read_iproute_table(ctx, PATH_IPROUTE_DIR "/rt_scopes", "ips_", 256);
read_iproute_table(ctx, PATH_IPROUTE_DIR "/rt_tables", "ipt_", 256);
#endif
}
int
sysdep_commit(struct config *new, struct config *old UNUSED)
{
log_switch(debug_flag, &new->logfiles, new->syslog_name);
return 0;
}
struct unix_conf_order {
struct conf_order co;
struct unix_ifs *ifs;
};
struct unix_ifs {
struct unix_ifs *up; /* Who included this file */
struct unix_ifs *next; /* Next file to include */
struct conf_state *state; /* Appropriate conf_state */
int fd; /* File descriptor */
byte depth; /* Include depth remaining, 0 = cannot include */
};
static int
unix_cf_read(struct conf_order *co, byte *dest, uint len)
{
struct unix_conf_order *uco = (struct unix_conf_order *) co;
ASSERT(uco->ifs->state == co->state);
if (uco->ifs->fd == -1)
uco->ifs->fd = open(co->state->name, O_RDONLY);
if (uco->ifs->fd < 0)
if (uco->ifs->up)
{
const char *fn = co->state->name;
co->state = uco->ifs->up->state; /* We want to raise this error in the parent file */
cf_error(co->ctx, "Unable to open included file %s: %m", fn);
}
else
cf_error(co->ctx, "Unable to open configuration file %s: %m", co->state->name);
int l = read(uco->ifs->fd, dest, len);
if (l < 0)
cf_error(co->ctx, "Read error: %m");
return l;
}
static void
unix_cf_include(struct conf_order *co, char *name, uint len)
{
struct unix_conf_order *uco = (struct unix_conf_order *) co;
if (!uco->ifs)
cf_error(co->ctx, "Max include depth reached");
byte new_depth = uco->ifs->depth - 1;
/* Includes are relative to the current file unless the path is absolute.
* Joining the current file dirname with the include relative path. */
char *patt;
if (*name != '/')
{
/* dlen is upper bound of current file dirname length */
int dlen = strlen(co->state->name);
char *dir = alloca(dlen + 1);
patt = alloca(dlen + len + 2);
/* dirname() may overwrite its argument */
memcpy(dir, co->state->name, dlen + 1);
sprintf(patt, "%s/%s", dirname(dir), name);
}
else
patt = name;
/* Skip globbing if there are no wildcards, mainly to get proper
response when the included config file is missing */
if (!strpbrk(name, "?*["))
{
struct unix_ifs *uifs = cf_alloc(co->ctx, sizeof(struct unix_ifs));
*uifs = (struct unix_ifs) {
.next = uco->ifs,
.up = uco->ifs,
.state = cf_new_state(co->ctx, patt),
.fd = -1,
.depth = new_depth,
};
co->state = uifs->state;
uco->ifs = uifs;
return;
}
/* Expand the pattern */
/* FIXME: glob() is not completely thread-safe, see the manpage */
glob_t g = {};
int rv = glob(patt, GLOB_ERR | GLOB_NOESCAPE, NULL, &g);
if (rv == GLOB_ABORTED)
cf_error(co->ctx, "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().
*/
struct unix_ifs *last_uifs = uco->ifs;
for (int i = g.gl_pathc - 1; i >= 0; i--)
{
char *fname = g.gl_pathv[i];
struct stat fs;
if (stat(fname, &fs) < 0)
{
globfree(&g);
cf_error(co->ctx, "Unable to stat included file %s: %m", fname);
}
if (fs.st_mode & S_IFDIR)
continue;
/* Prepare new stack item */
struct unix_ifs *uifs = cf_alloc(co->ctx, sizeof(struct unix_ifs));
*uifs = (struct unix_ifs) {
.next = last_uifs,
.up = uco->ifs,
.state = cf_new_state(co->ctx, fname),
.fd = -1,
.depth = new_depth,
};
last_uifs = uifs;
}
globfree(&g);
co->state = last_uifs->state;
uco->ifs = last_uifs;
return;
}
static int
unix_cf_outclude(struct conf_order *co)
{
struct unix_conf_order *uco = (struct unix_conf_order *) co;
close(uco->ifs->fd);
cf_free_state(co->ctx, uco->ifs->state);
/* No more files to read */
if (!uco->ifs->next)
return 1;
uco->ifs = uco->ifs->next;
co->state = uco->ifs->state;
return 0;
}
#define MAX_INCLUDE_DEPTH 8
typedef void (*cf_error_type)(struct conf_order *order, const char *msg, va_list args);
static struct config *
unix_read_config(char *name, cf_error_type arg_cf_error)
{
struct conf_state state = { .name = name };
struct unix_ifs uifs = {
.state = &state,
.depth = MAX_INCLUDE_DEPTH,
.fd = -1,
};
struct unix_conf_order uco = {
.co = {
.cf_read_hook = unix_cf_read,
.cf_include = unix_cf_include,
.cf_outclude = unix_cf_outclude,
.cf_error = arg_cf_error,
.state = &state,
},
.ifs = &uifs,
};
if (config_parse(&uco.co))
return uco.co.new_config;
else
return NULL;
}
static void
unix_cf_error_die(struct conf_order *order, const char *msg, va_list args)
{
die("%s, line %u: %V", order->state->name, order->state->lino, msg, &args);
}
struct config *
read_config(void)
{
return unix_read_config(config_name, unix_cf_error_die);
}
static void
unix_cf_error_log(struct conf_order *order, const char *msg, va_list args)
{
log(L_ERR "%s, line %u: %V", order->state->name, order->state->lino, msg, &args);
}
void
async_config(void)
{
log(L_INFO "Reconfiguration requested by SIGHUP");
struct config *conf = unix_read_config(config_name, unix_cf_error_log);
if (conf)
config_commit(conf, RECONFIG_HARD, 0);
}
static void
unix_cf_error_cli(struct conf_order *order, const char *msg, va_list args)
{
cli_msg(8002, "%s, line %d: %s", order->state->name, order->state->lino, msg, &args);
}
static struct config *
cmd_read_config(char *name)
{
if (!name)
name = config_name;
cli_msg(-2, "Reading configuration from %s", name);
return unix_read_config(name, unix_cf_error_cli);
}
void
cmd_check_config(char *name)
{
struct config *conf = cmd_read_config(name);
if (!conf)
return;
cli_msg(20, "Configuration OK");
config_free(conf);
}
static void
cmd_reconfig_msg(int r)
{
switch (r)
{
case CONF_DONE: cli_msg( 3, "Reconfigured"); break;
case CONF_PROGRESS: cli_msg( 4, "Reconfiguration in progress"); break;
case CONF_QUEUED: cli_msg( 5, "Reconfiguration already in progress, queueing new config"); break;
case CONF_UNQUEUED: cli_msg(17, "Reconfiguration already in progress, removing queued config"); break;
case CONF_CONFIRM: cli_msg(18, "Reconfiguration confirmed"); break;
case CONF_SHUTDOWN: cli_msg( 6, "Reconfiguration ignored, shutting down"); break;
case CONF_NOTHING: cli_msg(19, "Nothing to do"); break;
default: break;
}
}
/* Hack for scheduled undo notification */
cli *cmd_reconfig_stored_cli;
void
cmd_reconfig_undo_notify(void)
{
if (cmd_reconfig_stored_cli)
{
cli *c = cmd_reconfig_stored_cli;
cli_printf(c, CLI_ASYNC_CODE, "Config timeout expired, starting undo");
cli_write_trigger(c);
}
}
void
cmd_reconfig(char *name, int type, uint timeout)
{
if (cli_access_restricted())
return;
struct config *conf = cmd_read_config(name);
if (!conf)
return;
int r = config_commit(conf, type, timeout);
if ((r >= 0) && (timeout > 0))
{
cmd_reconfig_stored_cli = this_cli;
cli_msg(-22, "Undo scheduled in %d s", timeout);
}
cmd_reconfig_msg(r);
}
void
cmd_reconfig_confirm(void)
{
if (cli_access_restricted())
return;
int r = config_confirm();
cmd_reconfig_msg(r);
}
void
cmd_reconfig_undo(void)
{
if (cli_access_restricted())
return;
cli_msg(-21, "Undo requested");
int r = config_undo();
cmd_reconfig_msg(r);
}