blob: 05d25ac8e3ebc8469ae8012506578cf2fd101085 [file] [edit]
/*
*
drbdadm_parser.c a hand crafted parser
This file is part of DRBD by Philipp Reisner and Lars Ellenberg.
Copyright (C) 2006-2008, LINBIT Information Technologies GmbH
Copyright (C) 2006-2008, Philipp Reisner <philipp.reisner@linbit.com>
Copyright (C) 2006-2008, Lars Ellenberg <lars.ellenberg@linbit.com>
drbd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
drbd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with drbd; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glob.h>
#include <search.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include "drbdadm.h"
#include "linux/drbd.h"
#include "linux/drbd_limits.h"
#include "drbdtool_common.h"
#include "drbdadm_parser.h"
#include "shared_parser.h"
#include <config_flags.h>
YYSTYPE yylval;
/////////////////////
static int c_section_start;
static int parse_proxy_options(struct options *, struct options *);
static void parse_skip(void);
static struct d_resource *template_file(const char *res_name);
struct d_name *names_from_str(char* str)
{
struct d_name *names;
names = malloc(sizeof(struct d_name));
names->name = strdup(str);
return names;
}
char *_names_to_str_c(char* buffer, struct names *names, char c)
{
int n = 0;
struct d_name *name;
if (STAILQ_EMPTY(names)) {
snprintf(buffer, NAMES_STR_SIZE, "UNKNOWN");
return buffer;
}
name = STAILQ_FIRST(names);
while (1) {
n += snprintf(buffer + n, NAMES_STR_SIZE - n, "%s", name->name);
name = STAILQ_NEXT(name, link);
if (!name)
return buffer;
n += snprintf(buffer + n, NAMES_STR_SIZE - n, "%c", c);
}
}
char *_names_to_str(char* buffer, struct names *names)
{
return _names_to_str_c(buffer, names, ' ');
}
/*
* Determine if two hostnames are either fully qualified and equal,
* or one or both do not contain a domain name and the host names
* are equal. (Note that DNS names are not case sensitive.)
*/
bool hostnames_equal(const char *a, const char *b)
{
const char *domain_a = strchrnul(a, '.');
const char *domain_b = strchrnul(b, '.');
if (*domain_a && *domain_b) {
/* Both hostnames contain a domain part. */
return !strcasecmp(a, b);
} else {
/* One or both hostnames are "short". */
return domain_a - a == domain_b - b &&
!strncasecmp(a, b, domain_a - a);
}
}
int hostname_in_list(const char *name, struct names *names)
{
struct d_name *n;
STAILQ_FOREACH(n, names, link)
if (hostnames_equal(n->name, name))
return 1;
return 0;
}
void free_names(struct names *names)
{
struct d_name *n, *nf;
n = STAILQ_FIRST(names);
while (n) {
nf = STAILQ_NEXT(n, link);
free(n->name);
free(n);
n = nf;
}
}
void m_strtoll_range(const char *s, char def_unit,
const char *name,
unsigned long long min, unsigned long long max)
{
unsigned long long r = m_strtoll(s, def_unit);
char unit[] = { def_unit != '1' ? def_unit : 0, 0 };
if (min > r || r > max) {
err("%s:%d: %s %s => %llu%s out of range [%llu..%llu]%s.\n",
config_file, fline, name, s, r, unit, min, max, unit);
if (config_valid <= 1) {
config_valid = 0;
return;
}
}
if (DEBUG_RANGE_CHECK) {
err("%s:%d: %s %s => %llu%s in range [%llu..%llu]%s.\n",
config_file, fline, name, s, r, unit, min, max, unit);
}
}
void range_check(const enum range_checks what, const char *name,
char *value)
{
/*
* FIXME: Handle signed/unsigned values correctly by checking the
* F_field_name_IS_SIGNED defines.
*/
#define M_STRTOLL_RANGE(x) \
m_strtoll_range(value, DRBD_ ## x ## _SCALE, name, \
DRBD_ ## x ## _MIN, \
DRBD_ ## x ## _MAX)
switch (what) {
case R_MINOR_COUNT:
M_STRTOLL_RANGE(MINOR_COUNT);
break;
case R_DIALOG_REFRESH:
M_STRTOLL_RANGE(DIALOG_REFRESH);
break;
case R_PORT:
M_STRTOLL_RANGE(PORT);
break;
/* FIXME not yet implemented!
case R_META_IDX:
M_STRTOLL_RANGE(META_IDX);
break;
*/
case R_NODE_ID:
M_STRTOLL_RANGE(NODE_ID);
break;
default:
err("%s:%d: unknown range for %s => %s\n", config_file, fline, name, value);
break;
}
}
struct d_option *new_opt(char *name, char *value)
{
struct d_option *cn = calloc(1, sizeof(struct d_option));
/* err("%s:%d: %s = %s\n",config_file,line,name,value); */
cn->name = name;
cn->value = value;
return cn;
}
void pdperror(char *text)
{
config_valid = 0;
err("%s:%d: in proxy plugin section: %s.\n", config_file, line, text);
exit(E_CONFIG_INVALID);
}
static void pperror(struct d_proxy_info *proxy, char *text)
{
config_valid = 0;
err("%s:%d: in section: proxy on %s { ... } }: '%s' keyword missing.\n",
config_file, c_section_start, names_to_str(&proxy->on_hosts), text);
}
#define typecheck(type,x) \
({ type __dummy; \
typeof(x) __dummy2; \
(void)(&__dummy == &__dummy2); \
1; \
})
/*
* for check_uniq: check uniqueness of
* resource names, ip:port, node:disk and node:device combinations
* as well as resource:section ...
* binary tree to test for uniqueness of these values...
* Furthermore, the names of files that have been read are
* registered here, to avoid reading the same file multiple times.
* IMPORTANT:
* It is fine to point key and value to the same object within one entry, that
* is handled by free_bt_node, BUT DON'T point to the same objects from
* multiple entries (double free).
*/
void *global_btree = NULL;
/* some settings need only be unique within one resource definition. */
void *per_resource_btree = NULL;
int btree_key_cmp(const void *a, const void *b)
{
ENTRY *ka = (ENTRY*)a;
ENTRY *kb = (ENTRY*)b;
return strcmp((char*)ka->key, (char*)kb->key);
}
void free_bt_node(void *node)
{
ENTRY *e = node;
/* creative users of the tree might point key and data to the same object */
if (e->key != e->data)
free(e->key);
free(e->data);
free(e);
}
void free_btrees(void)
{
tdestroy(per_resource_btree, free_bt_node);
tdestroy(global_btree, free_bt_node);
}
void check_upr_init(void)
{
static int created = 0;
if (config_valid >= 2)
return;
if (created) {
tdestroy(per_resource_btree, free_bt_node);
}
per_resource_btree = NULL;
created = 1;
}
int vcheck_uniq_file_line(
const char *file, const int line,
void **bt, const char *what, const char *fmt, va_list ap)
{
int rv;
ENTRY *e, *f;
/* if we are done parsing the config file,
* switch off this paranoia */
if (config_valid >= 2)
return 1;
e = calloc(1, sizeof *e);
if (!e) {
err("calloc: %m\n");
exit(E_THINKO);
}
rv = vasprintf(&e->key, fmt, ap);
if (rv < 0) {
err("vasprintf: %m\n");
exit(E_THINKO);
}
if (EXIT_ON_CONFLICT && !what) {
err("Oops, unset argument in %s:%d.\n", __FILE__, __LINE__);
exit(E_THINKO);
}
m_asprintf((char **)&e->data, "%s:%u", file, line);
f = tfind(e, bt, btree_key_cmp);
if (f) {
if (what) {
ENTRY *ep = *(ENTRY **)f;
err("%s: conflicting use of %s '%s' ...\n%s: %s '%s' first used here.\n",
(char *)e->data, what, ep->key, (char *)ep->data, what, ep->key);
}
free(e->key);
free(e->data);
free(e);
config_valid = 0;
} else {
f = tsearch(e, bt, btree_key_cmp);
if (!f) {
err("tree entry (%s => %s) failed\n", e->key, (char *)e->data);
exit(E_THINKO);
}
f = NULL;
}
if (EXIT_ON_CONFLICT && f)
exit(E_CONFIG_INVALID);
return !f;
}
int vcheck_uniq(void **bt, const char *what, const char *fmt, va_list ap)
{
return vcheck_uniq_file_line(config_file, fline, bt, what, fmt, ap);
}
static void pe_expected(const char *exp)
{
const char *s = yytext;
err("%s:%u: Parse error: '%s' expected,\n\tbut got '%.20s%s'\n",
config_file, line, exp, s, strlen(s) > 20 ? "..." : "");
exit(E_CONFIG_INVALID);
}
static void check_string_error(int got)
{
const char *msg;
switch(got) {
case TK_ERR_STRING_TOO_LONG:
msg = "Token too long";
break;
case TK_ERR_DQSTRING_TOO_LONG:
msg = "Double quoted string too long";
break;
case TK_ERR_DQSTRING:
msg = "Unterminated double quoted string\n we don't allow embedded newlines\n ";
break;
default:
return;
}
err("%s:%u: %s >>>%.20s...<<<\n", config_file, line, msg, yytext);
exit(E_CONFIG_INVALID);
}
static void pe_expected_got(const char *exp, int got)
{
static char tmp[2] = "\0";
const char *s = yytext;
if (exp[0] == '\'' && exp[1] && exp[2] == '\'' && exp[3] == 0) {
tmp[0] = exp[1];
}
err("%s:%u: Parse error: '%s' expected,\n\tbut got '%.20s%s' (TK %d)\n",
config_file, line, tmp[0] ? tmp : exp, s, strlen(s) > 20 ? "..." : "", got);
exit(E_CONFIG_INVALID);
}
#define EXP(TOKEN1) \
({ \
int token; \
token = yylex(); \
if (token != TOKEN1) { \
if (TOKEN1 == TK_STRING) \
check_string_error(token); \
pe_expected_got( #TOKEN1, token); \
} \
token; \
})
static void parse_global(void)
{
fline = line;
check_uniq("global section", "global");
if (!STAILQ_EMPTY(&config)) {
err("%s:%u: You should put the global {} section\n\tin front of any resource {} section\n",
config_file, line);
}
EXP('{');
while (1) {
int token = yylex();
fline = line;
switch (token) {
case TK_UDEV_ALWAYS_USE_VNR:
global_options.udev_always_symlink_vnr = 1;
break;
case TK_DISABLE_IP_VERIFICATION:
global_options.disable_ip_verification = 1;
break;
case TK_MINOR_COUNT:
EXP(TK_INTEGER);
range_check(R_MINOR_COUNT, "minor-count", yylval.txt);
global_options.minor_count = atoi(yylval.txt);
break;
case TK_DIALOG_REFRESH:
EXP(TK_INTEGER);
range_check(R_DIALOG_REFRESH, "dialog-refresh",
yylval.txt);
global_options.dialog_refresh = atoi(yylval.txt);
break;
case TK_CMD_TIMEOUT_SHORT:
EXP(TK_INTEGER);
m_strtoll_range(yylval.txt, '1', "cmd-timeout-short", 0, 900);
global_options.cmd_timeout_short = atoi(yylval.txt);
break;
case TK_CMD_TIMEOUT_MEDIUM:
EXP(TK_INTEGER);
m_strtoll_range(yylval.txt, '1', "cmd-timeout-medium", 0, 900);
global_options.cmd_timeout_medium = atoi(yylval.txt);
break;
case TK_CMD_TIMEOUT_LONG:
EXP(TK_INTEGER);
m_strtoll_range(yylval.txt, '1', "cmd-timeout-long", 0, 900);
global_options.cmd_timeout_long = atoi(yylval.txt);
break;
case TK_USAGE_COUNT:
switch (yylex()) {
case TK_YES:
global_options.usage_count = UC_YES;
break;
case TK_NO:
global_options.usage_count = UC_NO;
break;
case TK_ASK:
global_options.usage_count = UC_ASK;
break;
default:
pe_expected("yes | no | ask");
}
break;
case '}':
return;
default:
pe_expected("dialog-refresh | minor-count | "
"disable-ip-verification");
}
EXP(';');
}
}
static char *check_deprecated_alias(char *name)
{
int i;
static struct {
char *old_name, *new_name;
} table[] = {
{ "outdate-peer", "fence-peer" },
{ "rate", "resync-rate" },
{ "after", "resync-after" },
};
for (i = 0; i < ARRAY_SIZE(table); i++) {
if (!strcmp(table[i].old_name, name))
return table[i].new_name;
}
return name;
}
static void pe_valid_enums(const char **map, int nr_enums)
{
int i, size = 0;
char *buffer, *p;
for (i = 0; i < nr_enums; i++) {
if (map[i])
size += strlen(map[i]) + 3;
}
assert(size >= 3);
buffer = alloca(size);
p = buffer;
for (i = 0; i < nr_enums; i++) {
if (map[i])
p += sprintf(p, "%s | ", map[i]);
}
buffer[size - 3] = 0; /* Eliminate last " | " */
err("Allowed values are: %s\n", buffer);
}
static void pe_field(struct field_def *field, enum check_codes e, char *value)
{
static const char *err_strings[] = {
[CC_NOT_AN_ENUM] = "not valid",
[CC_NOT_A_BOOL] = "not 'yes' or 'no'",
[CC_NOT_A_NUMBER] = "not a number",
[CC_TOO_SMALL] = "too small",
[CC_TOO_BIG] = "too big",
[CC_STR_TOO_LONG] = "too long",
[CC_NOT_AN_ENUM_NUM] = "not valid",
};
err("%s:%u: Parse error: while parsing value ('%.20s%s')\nfor %s. Value is %s.\n",
config_file, line,
value, strlen(value) > 20 ? "..." : "",
field->name, err_strings[e]);
if (e == CC_NOT_AN_ENUM)
pe_valid_enums(field->u.e.map, field->u.e.size);
else if (e == CC_STR_TOO_LONG)
err("max len: %u\n", field->u.s.max_len - 1);
/* else if (e == CC_NOT_AN_ENUM_NUM)
pe_valid_enum_num((field->u.en.map, field->u.en.map_size); */
if (config_valid <= 1)
config_valid = 0;
}
static void pe_options(struct context_def *options_def)
{
struct field_def *field;
char *buffer, *p;
int size = 0;
for (field = options_def->fields; field->name; field++)
size += strlen(field->name) + 3;
assert(size >= 3);
buffer = alloca(size);
p = buffer;
for (field = options_def->fields; field->name; field++)
p += sprintf(p, "%s | ", field->name);
buffer[size - 3] = 0; /* Eliminate last " | " */
pe_expected(buffer);
}
static struct field_def *find_field(bool *no_prefix, struct context_def *options_def,
const char *name)
{
struct field_def *field;
bool ignored_no_prefix;
if (no_prefix == NULL)
no_prefix = &ignored_no_prefix;
if (!strncmp(name, "no-", 3)) {
name += 3;
*no_prefix = true;
} else {
*no_prefix = false;
}
for (field = options_def->fields; field->name; field++) {
if (!strcmp(field->name, name))
return field;
}
return NULL;
}
static char *parse_option_value(struct field_def *field_def, bool no_prefix)
{
char *value;
int token;
token = yylex();
if (token == ';') {
value = strdup(no_prefix ? "no" : "yes");
} else {
enum check_codes e;
if (!field_def->checked_in_postparse) {
e = field_def->ops->check(field_def, yytext);
if (e != CC_OK)
pe_field(field_def, e, yytext);
}
value = strdup(yytext);
EXP(';');
}
return value;
}
/* The syncer section is deprecated. Distribute the options to the disk or net options. */
void parse_options_syncer(struct d_resource *res)
{
struct field_def *field_def;
bool no_prefix;
char *text, *value;
int token;
struct options *options = NULL;
c_section_start = line;
fline = line;
while (1) {
token = yylex();
fline = line;
if (token == '}')
return;
text = check_deprecated_alias(yytext);
field_def = find_field(&no_prefix, &show_net_options_ctx, text);
if (field_def) {
options = &res->net_options;
} else {
field_def = find_field(&no_prefix, &attach_cmd_ctx, text);
if (field_def) {
options = &res->disk_options;
} else {
field_def = find_field(&no_prefix, &resource_options_ctx,
text);
if (field_def)
options = &res->res_options;
else
pe_expected("a syncer option keyword");
}
}
value = parse_option_value(field_def, no_prefix);
insert_tail(options, new_opt((char *)field_def->name, value));
}
}
static struct options __parse_options(struct context_def *options_def,
void (*delegate)(void*),
void *delegate_context)
{
struct options options = STAILQ_HEAD_INITIALIZER(options);
struct field_def *field_def;
char *value;
bool no_prefix;
int token;
c_section_start = line;
fline = line;
while (1) {
token = yylex();
if (token == '}')
return options;
field_def = find_field(&no_prefix, options_def, yytext);
if (!field_def) {
if (delegate) {
delegate(delegate_context);
continue;
} else {
pe_options(options_def);
}
}
value = parse_option_value(field_def, no_prefix);
insert_tail(&options, new_opt((char *)field_def->name, value));
}
}
static struct options parse_options(struct context_def *options_def)
{
return __parse_options(options_def, NULL, NULL);
}
static void insert_pd_options_delegate(void *ctx)
{
struct options *options = ctx;
struct field_def *field_def;
bool no_prefix;
char *value;
field_def = find_field(&no_prefix, &peer_device_options_ctx, yytext);
if (!field_def)
pe_options(&peer_device_options_ctx);
value = parse_option_value(field_def, no_prefix);
insert_tail(options, new_opt((char *)field_def->name, value));
}
static void parse_disk_options(struct options *disk_options, struct options *peer_device_options)
{
*disk_options = __parse_options(&attach_cmd_ctx,
insert_pd_options_delegate,
peer_device_options);
}
static void __parse_address(struct d_address *a)
{
switch(yylex()) {
case TK_SCI: /* 'ssocks' was names 'sci' before. */
a->af = strdup("ssocks");
EXP(TK_IPADDR);
break;
case TK_SSOCKS:
case TK_SDP:
case TK_IPV4:
a->af = yylval.txt;
EXP(TK_IPADDR);
break;
case TK_IPV6:
a->af = yylval.txt;
EXP('[');
EXP(TK_IPADDR6);
break;
case TK_IPADDR:
a->af = strdup("ipv4");
break;
/* case '[': // Do not foster people's laziness ;)
EXP(TK_IPADDR6);
*af = strdup("ipv6");
break; */
default:
pe_expected("ssocks | sdp | ipv4 | ipv6 | <ipv4 address> ");
}
assert(a->af != NULL);
a->addr = yylval.txt;
if (!strcmp(a->af, "ipv6"))
EXP(']');
EXP(':');
EXP(TK_INTEGER);
a->port = yylval.txt;
range_check(R_PORT, "port", yylval.txt);
a->is_local_address = addr_scope_local(a->addr);
}
static void parse_address(struct names *on_hosts, struct d_address *address)
{
__parse_address(address);
EXP(';');
}
static void parse_hosts(struct names *hosts, char delimeter)
{
char errstr[20];
struct d_name *name;
int nr_hosts = 0;
int token;
while (1) {
token = yylex();
switch (token) {
case TK_STRING:
name = malloc(sizeof(struct d_name));
name->name = yylval.txt;
insert_tail(hosts, name);
nr_hosts++;
break;
default:
if (token == delimeter) {
if (nr_hosts == 0)
pe_expected_got("TK_STRING", token);
return;
} else {
sprintf(errstr, "TK_STRING | '%c'", delimeter);
pe_expected_got(errstr, token);
}
}
}
}
static struct d_proxy_info *parse_proxy_section(void)
{
struct d_proxy_info *proxy;
proxy = calloc(1, sizeof(struct d_proxy_info));
STAILQ_INIT(&proxy->on_hosts);
EXP(TK_ON);
parse_hosts(&proxy->on_hosts, '{');
while (1) {
switch (yylex()) {
case TK_INSIDE:
parse_address(&proxy->on_hosts, &proxy->inside);
break;
case TK_OUTSIDE:
parse_address(&proxy->on_hosts, &proxy->outside);
break;
case TK_OPTIONS:
parse_proxy_options(&proxy->options, &proxy->plugins);
break;
case '}':
goto break_loop;
default:
pe_expected("inside | outside");
}
}
break_loop:
if (!proxy->inside.addr)
pperror(proxy, "inside");
if (!proxy->outside.addr)
pperror(proxy, "outside");
return proxy;
}
void parse_meta_disk(struct d_volume *vol)
{
EXP(TK_STRING);
vol->meta_disk = yylval.txt;
if (strcmp("internal", yylval.txt) == 0) {
/* internal, flexible size */
vol->meta_index = strdup("internal");
EXP(';');
} else {
switch(yylex()) {
case '[':
EXP(TK_INTEGER);
/* external, static size */
vol->meta_index = yylval.txt;
EXP(']');
EXP(';');
break;
case ';':
/* external, flexible size */
vol->meta_index = strdup("flexible");
break;
default:
pe_expected("[ | ;");
}
}
}
static void check_minor_nonsense(const char *devname, const int explicit_minor)
{
if (!devname)
return;
/* if devname is set, it starts with /dev/drbd */
if (only_digits(devname + 9)) {
int m = strtol(devname + 9, NULL, 10);
if (m == explicit_minor)
return;
err("%s:%d: explicit minor number must match with device name\n"
"\tTry \"device /dev/drbd%u minor %u;\",\n"
"\tor leave off either device name or explicit minor.\n"
"\tArbitrary device names must start with /dev/drbd_\n"
"\tmind the '_'! (/dev/ is optional, but drbd_ is required)\n",
config_file, fline, explicit_minor, explicit_minor);
config_valid = 0;
return;
} else if (devname[9] == '_')
return;
err("%s:%d: arbitrary device name must start with /dev/drbd_\n"
"\tmind the '_'! (/dev/ is optional, but drbd_ is required)\n",
config_file, fline);
config_valid = 0;
return;
}
static void parse_device(struct names* on_hosts, struct d_volume *vol)
{
struct d_name *h;
int m;
switch (yylex()) {
case TK_STRING:
if (!strncmp("drbd", yylval.txt, 4)) {
m_asprintf(&vol->device, "/dev/%s", yylval.txt);
free(yylval.txt);
} else
vol->device = yylval.txt;
if (strncmp("/dev/drbd", vol->device, 9)) {
err("%s:%d: device name must start with /dev/drbd\n"
"\t(/dev/ is optional, but drbd is required)\n",
config_file, fline);
config_valid = 0;
/* no goto out yet,
* as that would additionally throw a parse error */
}
switch (yylex()) {
default:
pe_expected("minor | ;");
/* fall through */
case ';':
m = dt_minor_of_dev(vol->device);
if (m < 0) {
err("%s:%d: no minor given nor device name contains a minor number\n",
config_file, fline);
config_valid = 0;
}
vol->device_minor = m;
goto out;
case TK_MINOR:
; /* double fall through */
}
case TK_MINOR:
EXP(TK_INTEGER);
vol->device_minor = atoi(yylval.txt);
EXP(';');
/* if both device name and minor number are explicitly given,
* force /dev/drbd<minor-number> or /dev/drbd_<arbitrary> */
check_minor_nonsense(vol->device, vol->device_minor);
}
out:
if (!on_hosts)
return;
STAILQ_FOREACH(h, on_hosts, link) {
check_uniq_file_line(vol->v_config_file, vol->v_device_line,
"device-minor", "device-minor:%s:%u", h->name, vol->device_minor);
if (vol->device)
check_uniq_file_line(vol->v_config_file, vol->v_device_line,
"device", "device:%s:%s", h->name, vol->device);
}
}
struct d_volume *alloc_volume(void)
{
struct d_volume *vol;
vol = calloc(1, sizeof(struct d_volume));
if (vol == NULL) {
err("calloc: %m\n");
exit(E_EXEC_ERROR);
}
STAILQ_INIT(&vol->disk_options);
STAILQ_INIT(&vol->pd_options);
STAILQ_INIT(&vol->peer_devices);
return vol;
}
struct d_volume *volume0(struct volumes *volumes)
{
struct d_volume *vol = STAILQ_FIRST(volumes);
if (!vol) {
vol = alloc_volume();
vol->device_minor = -1;
vol->implicit = 1;
insert_head(volumes, vol);
return vol;
} else {
if (vol->vnr == 0 && STAILQ_NEXT(vol, link) == NULL && vol->implicit)
return vol;
config_valid = 0;
err("%s:%d: mixing explicit and implicit volumes is not allowed\n",
config_file, line);
return vol;
}
}
int parse_volume_stmt(struct d_volume *vol, struct names* on_hosts, int token)
{
if (!vol->v_config_file)
vol->v_config_file = config_file;
switch (token) {
case TK_DISK:
token = yylex();
switch (token) {
case TK_STRING:
vol->disk = yylval.txt;
EXP(';');
break;
case '{':
parse_disk_options(&vol->disk_options, &vol->pd_options);
break;
default:
check_string_error(token);
pe_expected_got( "TK_STRING | {", token);
}
vol->parsed_disk = 1;
vol->v_disk_line = fline;
break;
case TK_DEVICE:
parse_device(on_hosts, vol);
vol->parsed_device = 1;
vol->v_device_line = fline;
break;
case TK_META_DISK:
parse_meta_disk(vol);
vol->parsed_meta_disk = 1;
vol->v_meta_disk_line = fline;
break;
case TK_FLEX_META_DISK:
EXP(TK_STRING);
vol->meta_disk = yylval.txt;
if (strcmp("internal", yylval.txt) != 0) {
/* external, flexible ize */
vol->meta_index = strdup("flexible");
} else {
/* internal, flexible size */
vol->meta_index = strdup("internal");
}
EXP(';');
vol->parsed_meta_disk = 1;
vol->v_meta_disk_line = fline;
break;
case TK_SKIP:
parse_skip();
break;
default:
return 0;
}
return 1;
}
struct d_volume *parse_volume(int vnr, struct names* on_hosts)
{
struct d_volume *vol;
int token;
vol = alloc_volume();
vol->device_minor = -1;
vol->vnr = vnr;
EXP('{');
while (1) {
token = yylex();
if (token == '}')
break;
if (!parse_volume_stmt(vol, on_hosts, token))
pe_expected_got("device | disk | meta-disk | flex-meta-disk | }",
token);
}
return vol;
}
struct d_volume *parse_stacked_volume(int vnr)
{
struct d_volume *vol;
int token;
vol = alloc_volume();
vol->device_minor = -1;
vol->vnr = vnr;
EXP('{');
while (1) {
token = yylex();
switch (token) {
case TK_DEVICE:
parse_device(NULL, vol);
break;
case TK_DISK:
EXP('{');
parse_disk_options(&vol->disk_options, &vol->pd_options);
break;
case '}':
goto exit_loop;
default:
pe_expected_got("device | disk | }", token);
break;
}
}
exit_loop:
vol->meta_disk = strdup("internal");
vol->meta_index = strdup("internal");
return vol;
}
enum parse_host_section_flags {
REQUIRE_MINOR = 1,
BY_ADDRESS = 2,
};
static void parse_host_section(struct d_resource *res,
struct names *on_hosts,
enum parse_host_section_flags flags)
{
struct d_host_info *host;
struct d_name *h;
int in_braces = 1;
c_section_start = line;
fline = line;
host = calloc(1,sizeof(struct d_host_info));
STAILQ_INIT(&host->res_options);
STAILQ_INIT(&host->volumes);
host->on_hosts = *on_hosts;
host->config_line = c_section_start;
host->implicit = 0;
host->require_minor = flags & REQUIRE_MINOR ? 1 : 0;
if (flags & BY_ADDRESS) {
/* floating <address> {} */
char *fake_uname = NULL;
int token;
host->by_address = 1;
__parse_address(&host->address);
if (!strcmp(host->address.af, "ipv6"))
m_asprintf(&fake_uname, "ipv6 [%s]:%s", host->address.addr, host->address.port);
else
m_asprintf(&fake_uname, "%s:%s", host->address.addr, host->address.port);
insert_head(&host->on_hosts, names_from_str(fake_uname));
token = yylex();
switch(token) {
case '{':
break;
case ';':
in_braces = 0;
break;
default:
pe_expected_got("{ | ;", token);
}
}
STAILQ_FOREACH(h, on_hosts, link)
check_upr("host section", "%s: on %s", res->name, h->name);
insert_tail(&res->all_hosts, host);
while (in_braces) {
int token = yylex();
fline = line;
switch (token) {
case TK_DISK:
STAILQ_FOREACH(h, on_hosts, link)
check_upr("disk statement", "%s:%s:disk", res->name, h->name);
goto vol0stmt;
/* STAILQ_FOREACH(h, on_hosts)
check_uniq("disk", "disk:%s:%s", h->name, yylval.txt); */
case TK_DEVICE:
STAILQ_FOREACH(h, on_hosts, link)
check_upr("device statement", "%s:%s:device", res->name, h->name);
goto vol0stmt;
case TK_META_DISK:
STAILQ_FOREACH(h, on_hosts, link)
check_upr("meta-disk statement", "%s:%s:meta-disk", res->name, h->name);
goto vol0stmt;
case TK_FLEX_META_DISK:
STAILQ_FOREACH(h, on_hosts, link)
check_upr("meta-disk statement", "%s:%s:meta-disk", res->name, h->name);
goto vol0stmt;
break;
case TK_ADDRESS:
if (host->by_address) {
err("%s:%d: address statement not allowed for floating {} host sections\n",
config_file, fline);
config_valid = 0;
exit(E_CONFIG_INVALID);
}
STAILQ_FOREACH(h, on_hosts, link)
check_upr("address statement", "%s:%s:address", res->name, h->name);
parse_address(on_hosts, &host->address);
range_check(R_PORT, "port", host->address.port);
break;
case TK_PROXY:
host->proxy_compat_only = parse_proxy_section();
break;
case TK_VOLUME:
EXP(TK_INTEGER);
insert_volume(&host->volumes, parse_volume(atoi(yylval.txt), on_hosts));
break;
case TK_NODE_ID:
EXP(TK_INTEGER);
range_check(R_NODE_ID, "node-id", yylval.txt);
host->node_id = yylval.txt;
STAILQ_FOREACH(h, on_hosts, link)
check_upr("node-id statement", "%s:%s:node-id", res->name, h->name);
check_upr("node-id", "%s:%s", res->name, host->node_id);
EXP(';');
break;
case TK_OPTIONS:
EXP('{');
host->res_options = parse_options(&resource_options_ctx);
break;
case TK_SKIP:
parse_skip();
break;
case '}':
in_braces = 0;
break;
vol0stmt:
if (parse_volume_stmt(volume0(&host->volumes), on_hosts, token))
break;
/* else fall through */
default:
pe_expected("disk | device | address | meta-disk "
"| flexible-meta-disk | node-id | skip");
}
}
}
static void parse_skip()
{
int level = 0;
int token;
fline = line;
while ((token = yylex())) {
switch(token) {
case '{':
level++;
break;
case '}':
if (!--level)
return;
break;
}
}
if (!token) {
err("%s:%u: reached eof ""while parsing this skip block.\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
}
void parse_stacked_section(struct d_resource* res)
{
struct d_host_info *host;
struct d_name *h;
c_section_start = line;
fline = line;
host = calloc(1, sizeof(struct d_host_info));
STAILQ_INIT(&host->res_options);
STAILQ_INIT(&host->on_hosts);
STAILQ_INIT(&host->volumes);
insert_tail(&res->all_hosts, host);
EXP(TK_STRING);
check_uniq("stacked-on-top-of", "stacked:%s", yylval.txt);
host->lower_name = yylval.txt;
EXP('{');
while (1) {
switch(yylex()) {
case TK_DEVICE:
/* STAILQ_FOREACH(h, host->on_hosts)
check_upr("device statement", "%s:%s:device", res->name, h->name); */
parse_device(&host->on_hosts, volume0(&host->volumes));
volume0(&host->volumes)->meta_disk = strdup("internal");
volume0(&host->volumes)->meta_index = strdup("internal");
break;
case TK_ADDRESS:
STAILQ_FOREACH(h, &host->on_hosts, link)
check_upr("address statement", "%s:%s:address", res->name, h->name);
parse_address(NULL, &host->address);
range_check(R_PORT, "port", yylval.txt);
break;
case TK_PROXY:
host->proxy_compat_only = parse_proxy_section();
break;
case TK_VOLUME:
EXP(TK_INTEGER);
insert_volume(&host->volumes, parse_stacked_volume(atoi(yylval.txt)));
break;
case '}':
goto break_loop;
default:
pe_expected("device | address | proxy");
}
}
break_loop:
res->stacked_on_one = 1;
}
void startup_delegate(void *ctx)
{
struct d_resource *res = (struct d_resource *)ctx;
if (!strcmp(yytext, "become-primary-on")) {
/* err("Warn: Ignoring deprecated become-primary-on. Use automatic-promote\n"); */
int token;
do {
token = yylex();
} while (token != ';');
} else if (!strcmp(yytext, "stacked-timeouts")) {
res->stacked_timeouts = 1;
EXP(';');
} else
pe_expected("<an option keyword> | become-primary-on | stacked-timeouts");
}
void net_delegate(void *ctx)
{
enum pr_flags flags = (enum pr_flags)ctx;
if (!strcmp(yytext, "discard-my-data") && flags & PARSE_FOR_ADJUST) {
switch(yylex()) {
case TK_YES:
case TK_NO:
/* Ignore this option. */
EXP(';');
break;
case ';':
/* Ignore this option. */
return;
default:
pe_expected("yes | no | ;");
}
} else
pe_expected("an option keyword");
}
void proxy_delegate(void *ctx)
{
struct options *proxy_plugins = (struct options *)ctx;
int token;
struct options options = STAILQ_HEAD_INITIALIZER(options);
struct d_option *opt;
struct names line;
struct d_name *word;
opt = NULL;
token = yylex();
if (token != '{') {
err("%s:%d: expected \"{\" after \"proxy\" keyword\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
while (1) {
STAILQ_INIT(&line);
while (1) {
yylval.txt = NULL;
token = yylex();
if (token == ';')
break;
if (token == '}') {
if (STAILQ_EMPTY(&line))
goto out;
err("%s:%d: Missing \";\" before \"}\"\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
word = malloc(sizeof(struct d_name));
if (!word)
pdperror("out of memory.");
word->name = yylval.txt ? yylval.txt : strdup(yytext);
insert_tail(&line, word);
}
opt = calloc(1, sizeof(struct d_option));
if (!opt)
pdperror("out of memory.");
opt->name = strdup(names_to_str(&line));
insert_tail(&options, opt);
free_names(&line);
}
out:
if (proxy_plugins)
*proxy_plugins = options;
}
static int parse_proxy_options(struct options *proxy_options, struct options *proxy_plugins)
{
struct options opts;
EXP('{');
opts = __parse_options(&proxy_options_ctx,
proxy_delegate, proxy_plugins);
if (proxy_options)
*proxy_options = opts;
return 0;
}
int parse_proxy_options_section(struct d_proxy_info **pp)
{
int token;
struct d_proxy_info *proxy;
proxy = *pp ? *pp : calloc(1, sizeof(struct d_proxy_info));
token = yylex();
if (token != TK_PROXY) {
yyrestart(yyin); /* flushes flex's buffers */
return 1;
}
return parse_proxy_options(&proxy->options, &proxy->plugins);
}
static struct hname_address *parse_hname_address_pair(struct path *path, int prev_token)
{
struct hname_address *ha;
int token;
ha = calloc(1, sizeof(struct hname_address));
ha->config_line = line;
switch (prev_token) {
case TK_ADDRESS:
ha->name = "UNKNOWN"; /* updated in set_host_info_in_host_address_pairs() */
ha->by_address = 1;
goto parse_address;
case TK__THIS_HOST:
ha->name = "_this_host";
path->my_address = &ha->address;
goto parse_address;
case TK__REMOTE_HOST:
ha->name = "_remote_host";
path->connect_to = &ha->address;
goto parse_address;
default:
assert(0);
case TK_HOST:
break;
}
EXP(TK_STRING);
ha->name = yylval.txt;
token = yylex();
switch (token) {
case TK_ADDRESS:
parse_address:
__parse_address(&ha->address);
ha->parsed_address = 1;
goto parse_optional_via;
case TK_PORT:
EXP(TK_INTEGER);
ha->address.port = yylval.txt;
ha->parsed_port = 1;
parse_optional_via:
token = yylex();
if (token == TK_VIA) {
EXP(TK_PROXY);
ha->proxy = parse_proxy_section();
} else if (token != ';')
pe_expected_got( "via | ; ", token);
break;
case TK_VIA:
EXP(TK_PROXY);
ha->proxy = parse_proxy_section();
break;
case ';':
break;
default:
pe_expected_got( "address | port | ;", token);
}
return ha;
}
struct connection *alloc_connection()
{
struct connection *conn;
conn = calloc(1, sizeof(struct connection));
if (conn == NULL) {
err("calloc: %m\n");
exit(E_EXEC_ERROR);
}
STAILQ_INIT(&conn->paths);
STAILQ_INIT(&conn->net_options);
STAILQ_INIT(&conn->peer_devices);
STAILQ_INIT(&conn->pd_options);
return conn;
}
void free_connection(struct connection *connection)
{
free(connection);
}
struct peer_device *alloc_peer_device()
{
struct peer_device *peer_device;
peer_device = calloc(1, sizeof(*peer_device));
if (!peer_device) {
err("calloc: %m\n");
exit(E_EXEC_ERROR);
}
STAILQ_INIT(&peer_device->pd_options);
return peer_device;
}
static struct peer_device *parse_peer_device(int vnr)
{
struct peer_device *peer_device;
peer_device = alloc_peer_device();
peer_device->vnr = vnr;
peer_device->config_line = line;
EXP('{');
EXP(TK_DISK);
EXP('{');
peer_device->pd_options = parse_options(&peer_device_options_ctx);
EXP('}');
return peer_device;
}
static struct d_host_info *parse_peer_node_id(void)
{
struct d_host_info *host;
host = calloc(1,sizeof(struct d_host_info));
STAILQ_INIT(&host->res_options);
STAILQ_INIT(&host->volumes);
STAILQ_INIT(&host->on_hosts);
host->config_line = c_section_start;
host->implicit = 1;
host->require_minor = 0;
EXP(TK_INTEGER);
range_check(R_NODE_ID, "node-id", yylval.txt);
host->node_id = yylval.txt;
EXP(';');
return host;
}
struct path *alloc_path()
{
struct path *path;
path = calloc(1, sizeof(struct path));
if (path == NULL) {
err("calloc: %m\n");
exit(E_EXEC_ERROR);
}
STAILQ_INIT(&path->hname_address_pairs);
return path;
}
static struct path *path0(struct connection *conn)
{
struct path *path = STAILQ_FIRST(&conn->paths);
if (!path) {
path = alloc_path();
path->implicit = true;
path->config_line = line;
insert_tail(&conn->paths, path);
} else {
if (!path->implicit) {
config_valid = 0;
err("%s:%d: Explicit and implicit paths not allowed\n",
config_file, line);
}
}
return path;
}
static struct path *parse_path()
{
struct path *path;
int hosts = 0, token;
path = alloc_path();
path->config_line = line;
EXP('{');
while (1) {
token = yylex();
switch(token) {
case TK_ADDRESS:
case TK_HOST:
case TK__THIS_HOST:
case TK__REMOTE_HOST:
insert_tail(&path->hname_address_pairs, parse_hname_address_pair(path, token));
if (++hosts >= 3) {
err("%s:%d: only two 'host' keywords per path allowed\n",
config_file, fline);
config_valid = 0;
}
break;
case '}':
return path;
default:
pe_expected_got( "host | }", token);
}
}
}
static struct connection *parse_connection(enum pr_flags flags)
{
struct connection *conn;
struct peer_device *peer_device;
int hosts = 0, token;
struct path *path;
conn = alloc_connection();
conn->config_line = line;
token = yylex();
switch (token) {
case '{':
break;
case TK_STRING:
conn->name = yylval.txt;
EXP('{');
break;
default:
pe_expected_got( "<connection name> | {", token);
}
while (1) {
token = yylex();
switch(token) {
case TK_ADDRESS:
case TK_HOST:
case TK__THIS_HOST:
case TK__REMOTE_HOST:
path = path0(conn);
insert_tail(&path->hname_address_pairs, parse_hname_address_pair(path, token));
if (++hosts >= 3) {
err("%s:%d: only two 'host' keywords per connection allowed\n",
config_file, fline);
config_valid = 0;
}
break;
case TK__PEER_NODE_ID:
conn->peer = parse_peer_node_id();
break;
case TK_NET:
if (!STAILQ_EMPTY(&conn->net_options)) {
err("%s:%d: only one 'net' section per connection allowed\n",
config_file, fline);
config_valid = 0;
}
EXP('{');
conn->net_options = __parse_options(&show_net_options_ctx,
&net_delegate, (void *)flags);
break;
case TK_SKIP:
parse_skip();
break;
case TK_DISK:
EXP('{');
conn->pd_options = parse_options(&peer_device_options_ctx);
break;
case TK_VOLUME:
EXP(TK_INTEGER);
peer_device = parse_peer_device(atoi(yylval.txt));
peer_device->connection = conn;
STAILQ_INSERT_TAIL(&conn->peer_devices, peer_device, connection_link);
break;
case TK__IS_STANDALONE:
conn->is_standalone = 1;
EXP(';');
break;
case TK_PATH:
insert_tail(&conn->paths, parse_path());
break;
case '}':
if (STAILQ_EMPTY(&conn->paths) && !(flags & PARSE_FOR_ADJUST)) {
err("%s:%d: connection without a single path (maybe empty?) not allowed\n",
config_file, fline);
config_valid = 0;
}
return conn;
default:
pe_expected_got( "host | net | skip | }", token);
}
}
}
void parse_connection_mesh(struct d_resource *res, enum pr_flags flags)
{
struct mesh *mesh;
int token;
EXP('{');
mesh = calloc(1, sizeof(struct mesh));
STAILQ_INIT(&mesh->hosts);
STAILQ_INIT(&mesh->net_options);
while (1) {
token = yylex();
switch(token) {
case TK_HOSTS:
parse_hosts(&mesh->hosts, ';');
break;
case TK_NET:
if (!STAILQ_EMPTY(&mesh->net_options)) {
err("%s:%d: only one 'net' section allowed\n",
config_file, fline);
config_valid = 0;
}
EXP('{');
mesh->net_options =
__parse_options(&show_net_options_ctx,
&net_delegate,
(void *)flags);
break;
case '}':
insert_tail(&res->meshes, mesh);
return;
default:
pe_expected_got( "hosts | net | }", token);
}
}
}
struct d_resource* parse_resource(char* res_name, enum pr_flags flags)
{
struct field_def *proto_f;
char *proto_v;
struct d_resource* res;
struct names host_names;
struct options options;
int token;
check_upr_init();
check_uniq("resource section", res_name);
res = calloc(1, sizeof(struct d_resource));
STAILQ_INIT(&res->volumes);
STAILQ_INIT(&res->connections);
STAILQ_INIT(&res->all_hosts);
STAILQ_INIT(&res->net_options);
STAILQ_INIT(&res->disk_options);
STAILQ_INIT(&res->pd_options);
STAILQ_INIT(&res->res_options);
STAILQ_INIT(&res->startup_options);
STAILQ_INIT(&res->handlers);
STAILQ_INIT(&res->proxy_options);
STAILQ_INIT(&res->proxy_plugins);
STAILQ_INIT(&res->meshes);
res->name = res_name;
res->config_file = config_save;
res->start_line = line;
while(1) {
token = yylex();
fline = line;
switch(token) {
case TK_STRING:
if (strcmp(yylval.txt, "protocol"))
goto goto_default;
check_upr("protocol statement","%s: protocol",res->name);
proto_f = find_field(NULL, &net_options_ctx, yylval.txt);
proto_v = parse_option_value(proto_f, false);
insert_tail(&res->net_options, new_opt((char *)proto_f->name, proto_v));
break;
case TK_ON:
STAILQ_INIT(&host_names);
parse_hosts(&host_names, '{');
parse_host_section(res, &host_names, REQUIRE_MINOR);
break;
case TK_STACKED:
parse_stacked_section(res);
break;
case TK__THIS_HOST:
EXP('{');
STAILQ_INIT(&host_names);
insert_head(&host_names, names_from_str("_this_host"));
parse_host_section(res, &host_names, 0);
break;
case TK_FLOATING:
STAILQ_INIT(&host_names);
parse_host_section(res, &host_names, REQUIRE_MINOR + BY_ADDRESS);
break;
case TK_DISK:
switch (token=yylex()) {
case TK_STRING:{
/* open coded parse_volume_stmt() */
struct d_volume *vol = volume0(&res->volumes);
vol->disk = yylval.txt;
vol->parsed_disk = 1;
}
EXP(';');
break;
case '{':
check_upr("disk section", "%s:disk", res->name);
parse_disk_options(&options, &res->pd_options);
STAILQ_CONCAT(&res->disk_options, &options);
break;
default:
check_string_error(token);
pe_expected_got("TK_STRING | {", token);
}
break;
case TK_NET:
check_upr("net section", "%s:net", res->name);
EXP('{');
options = __parse_options(&show_net_options_ctx,
&net_delegate, (void *)flags);
STAILQ_CONCAT(&res->net_options, &options);
break;
case TK_SYNCER:
check_upr("syncer section", "%s:syncer", res->name);
EXP('{');
parse_options_syncer(res);
break;
case TK_STARTUP:
check_upr("startup section", "%s:startup", res->name);
EXP('{');
res->startup_options = __parse_options(&startup_options_ctx,
&startup_delegate,
res);
break;
case TK_HANDLER:
check_upr("handlers section", "%s:handlers", res->name);
EXP('{');
res->handlers = parse_options(&handlers_ctx);
break;
case TK_PROXY:
check_upr("proxy section", "%s:proxy", res->name);
parse_proxy_options(&res->proxy_options, &res->proxy_plugins);
break;
case TK_DEVICE:
check_upr("device statement", "%s:device", res->name);
case TK_META_DISK:
case TK_FLEX_META_DISK:
parse_volume_stmt(volume0(&res->volumes), NULL, token);
break;
case TK_VOLUME:
EXP(TK_INTEGER);
insert_volume(&res->volumes, parse_volume(atoi(yylval.txt), NULL));
break;
case TK_OPTIONS:
check_upr("resource options section", "%s:res_options", res->name);
EXP('{');
options = parse_options(&resource_options_ctx);
STAILQ_CONCAT(&res->res_options, &options);
break;
case TK_CONNECTION:
insert_tail(&res->connections, parse_connection(flags));
break;
case TK_CONNECTION_MESH:
parse_connection_mesh(res, flags);
break;
case TK_TEMPLATE_FILE:
res->template = template_file(res_name);
break;
case TK_SKIP:
parse_skip();
break;
case '}':
case 0:
goto exit_loop;
default:
goto_default:
pe_expected_got("protocol | on | disk | net | syncer |"
" startup | handlers | connection |"
" ignore-on | stacked-on-top-of | skip",token);
}
}
exit_loop:
if (flags == NO_HOST_SECT_ALLOWED && !STAILQ_EMPTY(&res->all_hosts)) {
config_valid = 0;
err("%s:%d: in the %s section, there are no host sections allowed.\n",
config_file, c_section_start, res->name);
}
return res;
}
/* Returns the "previous" count, ie. 0 if this file wasn't seen before. */
int was_file_already_seen(char *fn)
{
ENTRY *e;
char *real_path;
e = calloc(1, sizeof *e);
if (!e) {
err("calloc %m\n");
exit(E_THINKO);
}
real_path = realpath(fn, NULL);
if (!real_path) {
real_path = strdup(fn);
if (!real_path) {
err("strdup %m");
free(e);
exit(E_THINKO);
}
}
e->key = real_path;
e->data = real_path;
if (tfind(e, &global_btree, btree_key_cmp)) {
/* Can be freed, it's just a queried key. */
free(real_path);
free(e);
return 1;
}
if (!tsearch(e, &global_btree, btree_key_cmp)) {
err("tree entry (%s => %s) failed\n", e->key, (char *)e->data);
free(real_path);
free(e);
exit(E_THINKO);
}
/* Must not be freed, because it's still referenced by the binary tree. */
/* free(real_path); */
return 0;
}
/* In order to allow relative paths in include statements we change
* directory to the location of the current configuration file.
* Before we start parsing, we canonicalize the full path name stored in
* config_save, which means config_save always contains at least one slash.
* Unless we are currently parsing STDIN (then it is the fixed string STDIN).
*/
static int pushd_to_current_config_file_unless_stdin(void)
{
char *last_slash, *tmp;
/* config_save was canonicalized before, unless it is STDIN */
tmp = strdupa(config_save);
last_slash = strrchr(tmp, '/');
if (!last_slash)
/* Currently parsing stdin, stay where we are.
* FIXME introduce DRBD_INCLUDE_PATH?
* I don't feel comfortable "trusting" the current directory
* for relative include file paths.
*/
return -1;
/* If last_slash == tmp, config_save is in the top level directory. */
if (last_slash == tmp)
tmp = "/";
else
*last_slash = 0;
return pushd(tmp);
}
static struct d_resource *template_file(const char *res_name)
{
struct d_resource *template = NULL;
char *file_name;
FILE *f;
int cwd;
cwd = pushd_to_current_config_file_unless_stdin();
EXP(TK_STRING);
file_name = yylval.txt;
EXP(';');
f = fopen(file_name, "re");
if (f) {
struct include_file_buffer buffer;
char *tn = ssprintf("template-%s", res_name);
save_parse_context(&buffer, f, file_name);
EXP(TK_COMMON);
EXP('{');
template = parse_resource(tn, NO_HOST_SECT_ALLOWED);
restore_parse_context(&buffer);
fclose(f);
} else {
err("%s:%d: Failed to open template file '%s'.\n",
config_save, line, file_name);
config_valid = 0;
}
popd(cwd);
return template;
}
void include_stmt(char *str)
{
glob_t glob_buf;
int cwd;
FILE *f;
size_t i;
int r;
cwd = pushd_to_current_config_file_unless_stdin();
r = glob(str, 0, NULL, &glob_buf);
if (r == 0) {
for (i=0; i<glob_buf.gl_pathc; i++) {
if (was_file_already_seen(glob_buf.gl_pathv[i]))
continue;
f = fopen(glob_buf.gl_pathv[i], "re");
if (f) {
include_file(f, strdup(glob_buf.gl_pathv[i]));
fclose(f);
} else {
err("%s:%d: Failed to open include file '%s'.\n",
config_save, line, glob_buf.gl_pathv[i]);
config_valid = 0;
}
}
globfree(&glob_buf);
} else if (r == GLOB_NOMATCH) {
if (!strchr(str, '?') && !strchr(str, '*') && !strchr(str, '[')) {
err("%s:%d: Failed to open include file '%s'.\n",
config_save, line, str);
config_valid = 0;
}
} else {
err("glob() failed: %d\n", r);
exit(E_USAGE);
}
popd(cwd);
}
void my_parse(void)
{
/* Remember that we're reading that file. */
was_file_already_seen(config_file);
while (1) {
int token = yylex();
fline = line;
switch(token) {
case TK_GLOBAL:
parse_global();
break;
case TK_COMMON:
EXP('{');
common = parse_resource("common",NO_HOST_SECT_ALLOWED);
break;
case TK_RESOURCE:
EXP(TK_STRING);
ensure_sanity_of_res_name(yylval.txt);
EXP('{');
insert_tail(&config, parse_resource(yylval.txt, 0));
break;
case TK_SKIP:
parse_skip();
break;
case TK_INCLUDE:
EXP(TK_STRING);
EXP(';');
include_stmt(yylval.txt);
break;
case 0:
return;
default:
pe_expected("global | common | resource | skip | include");
}
}
}
int check_uniq(const char *what, const char *fmt, ...)
{
int rv;
va_list ap;
va_start(ap, fmt);
rv = vcheck_uniq(&global_btree, what, fmt, ap);
va_end(ap);
return rv;
}
int check_uniq_file_line(const char *file, const int line, const char *what, const char *fmt, ...)
{
int rv;
va_list ap;
va_start(ap, fmt);
rv = vcheck_uniq_file_line(file, line, &global_btree, what, fmt, ap);
va_end(ap);
return rv;
}
/* unique per resource */
int check_upr(const char *what, const char *fmt, ...)
{
int rv;
va_list ap;
va_start(ap, fmt);
rv = vcheck_uniq(&per_resource_btree, what, fmt, ap);
va_end(ap);
return rv;
}