blob: adb9e3ddc102625e1f8b8f1b4584cc4472d4cb17 [file] [log] [blame]
/*
*
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_limits.h"
#include "drbdtool_common.h"
#include "drbdadm_parser.h"
#include "shared_parser.h"
YYSTYPE yylval;
/////////////////////
static int c_section_start;
static int parse_proxy_options(struct d_option **, struct d_option **);
void my_parse(void);
struct d_name *names_from_str(char* str)
{
struct d_name *names;
names = malloc(sizeof(struct d_name));
names->next = NULL;
names->name = strdup(str);
return names;
}
char *_names_to_str_c(char* buffer, struct d_name *names, char c)
{
int n = 0;
if (!names) {
snprintf(buffer, NAMES_STR_SIZE, "UNKNOWN");
return buffer;
}
while (1) {
n += snprintf(buffer + n, NAMES_STR_SIZE - n, "%s", names->name);
names = names->next;
if (!names)
return buffer;
n += snprintf(buffer + n, NAMES_STR_SIZE - n, "%c", c);
}
}
char *_names_to_str(char* buffer, struct d_name *names)
{
return _names_to_str_c(buffer, names, ' ');
}
int name_in_names(char *name, struct d_name *names)
{
while (names) {
if (!strcmp(names->name, name))
return 1;
names = names->next;
}
return 0;
}
void free_names(struct d_name *names)
{
struct d_name *nf;
while (names) {
nf = names->next;
free(names->name);
free(names);
names = nf;
}
}
static void append_names(struct d_name **head, struct d_name ***last, struct d_name *to_copy)
{
struct d_name *new;
while (to_copy) {
new = malloc(sizeof(struct d_name));
if (!*head)
*head = new;
new->name = strdup(to_copy->name);
new->next = NULL;
if (*last)
**last = new;
*last = &new->next;
to_copy = to_copy->next;
}
}
struct d_name *concat_names(struct d_name *to_copy1, struct d_name *to_copy2)
{
struct d_name *head = NULL, **last = NULL;
append_names(&head, &last, to_copy1);
append_names(&head, &last, to_copy2);
return head;
}
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)
{
char proto = 0;
/*
* 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_NO_CHECK:
break;
default:
err("%s:%d: unknown range for %s => %s\n", config_file, fline, name, value);
break;
case R_MINOR_COUNT:
M_STRTOLL_RANGE(MINOR_COUNT);
break;
case R_DIALOG_REFRESH:
M_STRTOLL_RANGE(DIALOG_REFRESH);
break;
case R_DISK_SIZE:
M_STRTOLL_RANGE(DISK_SIZE);
break;
case R_TIMEOUT:
M_STRTOLL_RANGE(TIMEOUT);
break;
case R_CONNECT_INT:
M_STRTOLL_RANGE(CONNECT_INT);
break;
case R_PING_INT:
M_STRTOLL_RANGE(PING_INT);
break;
case R_MAX_BUFFERS:
M_STRTOLL_RANGE(MAX_BUFFERS);
break;
case R_MAX_EPOCH_SIZE:
M_STRTOLL_RANGE(MAX_EPOCH_SIZE);
break;
case R_SNDBUF_SIZE:
M_STRTOLL_RANGE(SNDBUF_SIZE);
break;
case R_RCVBUF_SIZE:
M_STRTOLL_RANGE(RCVBUF_SIZE);
break;
case R_KO_COUNT:
M_STRTOLL_RANGE(KO_COUNT);
break;
case R_RATE:
M_STRTOLL_RANGE(RESYNC_RATE);
break;
case R_AL_EXTENTS:
/* ignore; auto-clamped by kernel.
* M_STRTOLL_RANGE(AL_EXTENTS);
*/
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_WFC_TIMEOUT:
M_STRTOLL_RANGE(WFC_TIMEOUT);
break;
case R_DEGR_WFC_TIMEOUT:
M_STRTOLL_RANGE(DEGR_WFC_TIMEOUT);
break;
case R_OUTDATED_WFC_TIMEOUT:
M_STRTOLL_RANGE(OUTDATED_WFC_TIMEOUT);
break;
case R_C_PLAN_AHEAD:
M_STRTOLL_RANGE(C_PLAN_AHEAD);
break;
case R_C_DELAY_TARGET:
M_STRTOLL_RANGE(C_DELAY_TARGET);
break;
case R_C_FILL_TARGET:
M_STRTOLL_RANGE(C_FILL_TARGET);
break;
case R_C_MAX_RATE:
M_STRTOLL_RANGE(C_MAX_RATE);
break;
case R_C_MIN_RATE:
M_STRTOLL_RANGE(C_MIN_RATE);
break;
case R_CONG_FILL:
M_STRTOLL_RANGE(CONG_FILL);
break;
case R_CONG_EXTENTS:
M_STRTOLL_RANGE(CONG_EXTENTS);
break;
case R_PROTOCOL:
if (value && value[0] && value[1] == 0) {
proto = value[0] & ~0x20; /* toupper */
if (proto == 'A' || proto == 'B' || proto == 'C')
value[0] = proto;
else
proto = 0;
}
if (!proto && config_valid <= 1) {
config_valid = 0;
err("unknown protocol '%s', should be one of A,B,C\n", 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;
}
static void derror(struct d_host_info *host, struct d_resource *res, char *text)
{
config_valid = 0;
err("%s:%d: in resource %s, on %s { ... }:"
" '%s' keyword missing.\n",
config_file, c_section_start, res->name, names_to_str(host->on_hosts), text);
}
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_host_info *host, struct d_proxy_info *proxy, char *text)
{
config_valid = 0;
err("%s:%d: in section: on %s { proxy on %s { ... } }: '%s' keyword missing.\n",
config_file, c_section_start, names_to_str(host->on_hosts),
names_to_str(proxy->on_hosts), text);
}
#define typecheck(type,x) \
({ type __dummy; \
typeof(x) __dummy2; \
(void)(&__dummy == &__dummy2); \
1; \
})
#define for_each_host(h_,hosts_) \
for ( ({ typecheck(struct d_name*, h_); \
h_ = hosts_; }); \
h_; h_ = h_->next)
/*
* for check_uniq: check uniqueness of
* resource names, ip:port, node:disk and node:device combinations
* as well as resource:section ...
* hash table to test for uniqueness of these values...
* 256 (max minors)
* *(
* 2 (host sections) * 4 (res ip:port node:disk node:device)
* + 4 (other sections)
* + some more,
* if we want to check for scoped uniqueness of *every* option
* )
* since nobody (?) will actually use more than a dozen minors,
* this should be more than enough.
*
* Furthermore, the names of files that have been read are
* registered here, to avoid reading the same file multiple times.
*/
struct hsearch_data global_htable;
void check_uniq_init(void)
{
memset(&global_htable, 0, sizeof(global_htable));
if (!hcreate_r(256 * ((2 * 4) + 4), &global_htable)) {
err("Insufficient memory.\n");
exit(E_EXEC_ERROR);
};
}
/* some settings need only be unique within one resource definition.
* we need currently about 8 + (number of host) * 8 entries,
* 200 should be much more than enough. */
struct hsearch_data per_resource_htable;
void check_upr_init(void)
{
static int created = 0;
if (config_valid >= 2)
return;
if (created)
hdestroy_r(&per_resource_htable);
memset(&per_resource_htable, 0, sizeof(per_resource_htable));
if (!hcreate_r(256, &per_resource_htable)) {
err("Insufficient memory.\n");
exit(E_EXEC_ERROR);
};
created = 1;
}
/* FIXME
* strictly speaking we don't need to check for uniqueness of disk and device names,
* but for uniqueness of their major:minor numbers ;-)
*/
int vcheck_uniq(struct hsearch_data *ht, const char *what, const char *fmt, va_list ap)
{
int rv;
ENTRY e, *ep;
e.key = e.data = ep = NULL;
/* if we are done parsing the config file,
* switch off this paranoia */
if (config_valid >= 2)
return 1;
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", config_file, fline);
hsearch_r(e, FIND, &ep, ht);
//err("FIND %s: %p\n", e.key, ep);
if (ep) {
if (what) {
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);
config_valid = 0;
} else {
//err("ENTER %s\t=>\t%s\n", e.key, (char *)e.data);
hsearch_r(e, ENTER, &ep, ht);
if (!ep) {
err("hash table entry (%s => %s) failed\n", e.key, (char *)e.data);
exit(E_THINKO);
}
ep = NULL;
}
if (EXIT_ON_CONFLICT && ep)
exit(E_CONFIG_INVALID);
return !ep;
}
void check_meta_disk(struct d_volume *vol, struct d_host_info *host)
{
struct d_name *h;
/* when parsing "drbdsetup show[-all]" output,
* a detached volume will only have device/minor,
* but no disk or meta disk. */
if (vol->meta_disk == NULL)
return;
if (strcmp(vol->meta_disk, "internal") != 0) {
/* index either some number, or "flexible" */
for_each_host(h, host->on_hosts)
check_uniq("meta-disk", "%s:%s[%s]", h->name, vol->meta_disk, vol->meta_index);
}
}
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 expect_STRING_or_INT(void)
{
int token = yylex();
switch(token) {
case TK_INTEGER:
case TK_STRING:
break;
case TK_ON:
yylval.txt = strdup(yytext);
break;
default:
check_string_error(token);
pe_expected_got("TK_STRING | TK_INTEGER", token);
}
}
static void parse_global(void)
{
fline = line;
check_uniq("global section", "global");
if (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 void check_and_change_deprecated_alias(char **name, int token)
{
int i;
static struct {
enum yytokentype token;
char *old_name, *new_name;
} table[] = {
{ TK_HANDLER_OPTION, "outdate-peer", "fence-peer" },
{ TK_DISK_OPTION, "rate", "resync-rate" },
{ TK_DISK_OPTION, "after", "resync-after" },
};
for (i = 0; i < ARRAY_SIZE(table); i++) {
if (table[i].token == token &&
!strcmp(table[i].old_name, *name)) {
free(*name);
*name = strdup(table[i].new_name);
}
}
}
/* The syncer section is deprecated. Distribute the options to the disk or net options. */
void parse_options_syncer(struct d_resource *res)
{
char *opt_name;
int token;
enum range_checks rc;
struct d_option **options = NULL, *current_option = NULL;
c_section_start = line;
fline = line;
while (1) {
token = yylex();
fline = line;
if (token >= TK_GLOBAL && !(token & TK_SYNCER_OLD_OPT))
pe_expected("a syncer option keyword");
token &= ~TK_SYNCER_OLD_OPT;
switch (token) {
case TK_NET_FLAG:
case TK_NET_NO_FLAG:
case TK_NET_OPTION:
options = &res->net_options;
break;
case TK_DISK_FLAG:
case TK_DISK_NO_FLAG:
case TK_DISK_OPTION:
options = &res->disk_options;
break;
case TK_RES_OPTION:
options = &res->res_options;
break;
case '}':
return;
default:
pe_expected("a syncer option keyword");
}
opt_name = yylval.txt;
switch (token) {
case TK_NET_FLAG:
case TK_DISK_FLAG:
token = yylex();
switch(token) {
case TK_NO:
current_option = new_opt(opt_name, strdup("no"));
*options = APPEND(*options, current_option);
token = yylex();
break;
default:
current_option = new_opt(opt_name, strdup("yes"));
*options = APPEND(*options, current_option);
if (token == TK_YES)
token = yylex();
break;
}
break;
case TK_NET_NO_FLAG:
case TK_DISK_NO_FLAG:
/* Backward compatibility with the old config file syntax. */
assert(!strncmp(opt_name, "no-", 3));
current_option = new_opt(strdup(opt_name + 3), strdup("no"));
*options = APPEND(*options, current_option);
free(opt_name);
token = yylex();
break;
case TK_NET_OPTION:
case TK_DISK_OPTION:
case TK_RES_OPTION:
check_and_change_deprecated_alias(&opt_name, token);
rc = yylval.rc;
expect_STRING_or_INT();
range_check(rc, opt_name, yylval.txt);
current_option = new_opt(opt_name, yylval.txt);
*options = APPEND(*options, current_option);
token = yylex();
break;
}
switch (token) {
case ';':
break;
default:
pe_expected(";");
}
}
}
static struct d_option *parse_options_d(int token_flag, int token_no_flag, int token_option,
int token_delegate, void (*delegate)(void*),
void *ctx)
{
char *opt_name;
int token, token_group;
enum range_checks rc;
struct d_option *options = NULL, *current_option = NULL;
c_section_start = line;
fline = line;
while (1) {
token_group = yylex();
/* Keep the higher bits in token_option, remove them from token. */
token = REMOVE_GROUP_FROM_TOKEN(token_group);
fline = line;
opt_name = yylval.txt;
if (token <= 0) {
pe_expected("an option");
} else if (token == token_flag) {
switch(yylex()) {
case TK_YES:
current_option = new_opt(opt_name, strdup("yes"));
options = APPEND(options, current_option);
break;
case TK_NO:
current_option = new_opt(opt_name, strdup("no"));
options = APPEND(options, current_option);
break;
case ';':
/* Flag value missing; assume yes. */
options = APPEND(options, new_opt(opt_name, strdup("yes")));
continue;
default:
pe_expected("yes | no | ;");
}
} else if (token == token_no_flag) {
/* Backward compatibility with the old config file syntax. */
assert(!strncmp(opt_name, "no-", 3));
current_option = new_opt(strdup(opt_name + 3), strdup("no"));
options = APPEND(options, current_option);
free(opt_name);
} else if (token == token_option ||
GET_TOKEN_GROUP(token_option & token_group)) {
check_and_change_deprecated_alias(&opt_name, token_option);
rc = yylval.rc;
expect_STRING_or_INT();
range_check(rc, opt_name, yylval.txt);
current_option = new_opt(opt_name, yylval.txt);
options = APPEND(options, current_option);
} else if (ctx && (token == token_delegate ||
GET_TOKEN_GROUP(token_delegate & token_group))) {
delegate(ctx);
continue;
} else if (token == TK_DEPRECATED_OPTION) {
/* err("Warn: Ignoring deprecated option '%s'\n", yylval.txt); */
expect_STRING_or_INT();
} else if (token == '}') {
return options;
} else {
pe_expected("an option keyword");
}
EXP(';');
}
}
static struct d_option *parse_options(int token_flag, int token_no_flag, int token_option)
{
return parse_options_d(token_flag, token_no_flag, token_option, 0, NULL, NULL);
}
static void __parse_address(char** addr, char** port, char** af)
{
switch(yylex()) {
case TK_SCI: /* 'ssocks' was names 'sci' before. */
if (af)
*af = strdup("ssocks");
EXP(TK_IPADDR);
break;
case TK_SSOCKS:
case TK_SDP:
case TK_IPV4:
if (af)
*af = yylval.txt;
EXP(TK_IPADDR);
break;
case TK_IPV6:
if (af)
*af = yylval.txt;
EXP('[');
EXP(TK_IPADDR6);
break;
case TK_IPADDR:
if (af)
*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> ");
}
if (addr)
*addr = yylval.txt;
if (af && !strcmp(*af, "ipv6"))
EXP(']');
EXP(':');
EXP(TK_INTEGER);
if (port)
*port = yylval.txt;
range_check(R_PORT, "port", yylval.txt);
}
static void parse_address(struct d_name *on_hosts, char** addr, char** port, char** af)
{
struct d_name *h;
__parse_address(addr, port, af);
if (addr_scope_local(*addr))
for_each_host(h, on_hosts)
check_uniq("IP", "%s:%s:%s", h->name, *addr, *port);
else
check_uniq("IP", "%s:%s", *addr, *port);
EXP(';');
}
static void parse_hosts(struct d_name **pnp, char delimeter)
{
char errstr[20];
struct d_name *name;
int hosts = 0;
int token;
while (1) {
token = yylex();
switch (token) {
case TK_STRING:
name = malloc(sizeof(struct d_name));
name->name = yylval.txt;
name->next = NULL;
*pnp = name;
pnp = &name->next;
hosts++;
break;
default:
if (token == delimeter) {
if (!hosts)
pe_expected_got("TK_STRING", token);
return;
} else {
sprintf(errstr, "TK_STRING | '%c'", delimeter);
pe_expected_got(errstr, token);
}
}
}
}
static void parse_proxy_section(struct d_host_info *host)
{
struct d_proxy_info *proxy;
proxy=calloc(1,sizeof(struct d_proxy_info));
host->proxy = proxy;
EXP(TK_ON);
parse_hosts(&proxy->on_hosts, '{');
while (1) {
switch (yylex()) {
case TK_INSIDE:
parse_address(proxy->on_hosts, &proxy->inside_addr, &proxy->inside_port, &proxy->inside_af);
break;
case TK_OUTSIDE:
parse_address(proxy->on_hosts, &proxy->outside_addr, &proxy->outside_port, &proxy->outside_af);
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(host, proxy, "inside");
if (!proxy->outside_addr)
pperror(host, proxy, "outside");
return;
}
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 d_name* 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:
for_each_host(h, on_hosts) {
check_uniq("device-minor", "device-minor:%s:%u", h->name, vol->device_minor);
if (vol->device)
check_uniq("device", "device:%s:%s", h->name, vol->device);
}
}
struct d_volume *find_volume(struct d_volume *vol, int vnr)
{
while (vol) {
if (vol->vnr == vnr)
return vol;
vol = vol->next;
}
return NULL;
}
struct d_volume *volume0(struct d_volume **volp)
{
struct d_volume *vol;
if (!*volp) {
vol = calloc(1, sizeof(struct d_volume));
vol->device_minor = -1;
*volp = vol;
vol->implicit = 1;
return vol;
} else {
vol = *volp;
if (vol->vnr == 0 && vol->next == 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 d_name* on_hosts, int token)
{
switch (token) {
case TK_DISK:
token = yylex();
switch (token) {
case TK_STRING:
vol->disk = yylval.txt;
EXP(';');
break;
case '{':
vol->disk_options = parse_options(TK_DISK_FLAG,
TK_DISK_NO_FLAG,
TK_DISK_OPTION);
break;
default:
check_string_error(token);
pe_expected_got( "TK_STRING | {", token);
}
break;
case TK_DEVICE:
parse_device(on_hosts, vol);
break;
case TK_META_DISK:
parse_meta_disk(vol);
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(';');
break;
default:
return 0;
}
return 1;
}
struct d_volume *parse_volume(int vnr, struct d_name* on_hosts)
{
struct d_volume *vol;
int token;
vol = calloc(1,sizeof(struct d_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;
vol = calloc(1,sizeof(struct d_volume));
vol->device_minor = -1;
vol->vnr = vnr;
EXP('{');
EXP(TK_DEVICE);
parse_device(NULL, vol);
EXP('}');
vol->meta_disk = strdup("internal");
vol->meta_index = strdup("internal");
return vol;
}
void inherit_volumes(struct d_volume *from, struct d_host_info *host)
{
struct d_volume *s, *t;
struct d_name *h;
for (s = from; s != NULL ; s = s->next) {
t = find_volume(host->volumes, s->vnr);
if (!t) {
t = calloc(1, sizeof(struct d_volume));
t->device_minor = -1;
t->vnr = s->vnr;
t->implicit = s->implicit;
host->volumes = INSERT_SORTED(host->volumes, t, vnr);
}
if (!t->disk && s->disk) {
t->disk = strdup(s->disk);
for_each_host(h, host->on_hosts)
check_uniq("disk", "disk:%s:%s", h->name, t->disk);
}
if (!t->device && s->device)
t->device = strdup(s->device);
if (t->device_minor == -1U && s->device_minor != -1U) {
t->device_minor = s->device_minor;
for_each_host(h, host->on_hosts)
check_uniq("device-minor", "device-minor:%s:%d", h->name, t->device_minor);
}
if (!t->meta_disk && s->meta_disk) {
t->meta_disk = strdup(s->meta_disk);
if (s->meta_index)
t->meta_index = strdup(s->meta_index);
}
}
}
void check_volume_complete(struct d_resource *res, struct d_host_info *host, struct d_volume *vol)
{
if (!vol->device && vol->device_minor == -1U)
derror(host, res, "device");
if (!vol->disk)
derror(host, res, "disk");
if (!vol->meta_disk)
derror(host, res, "meta-disk");
if (!vol->meta_index)
derror(host, res, "meta-index");
}
void check_volumes_complete(struct d_resource *res, struct d_host_info *host)
{
struct d_volume *vol = host->volumes;
unsigned vnr = -1U;
bool any_implicit = false;
bool any_non_zero_vnr = false;
while (vol) {
if (vnr == -1U || vnr < vol->vnr)
vnr = vol->vnr;
else
err("internal error: in %s: unsorted volumes list\n", res->name);
any_implicit |= vol->implicit;
any_non_zero_vnr |= vol->vnr != 0;
check_volume_complete(res, host, vol);
vol = vol->next;
}
if (any_implicit && any_non_zero_vnr) {
err("%s:%d: in resource %s: you must not mix implicit any explicit volumes\n",
config_file, line, res->name);
config_valid = 0;
}
}
void check_volume_sets_equal(struct d_resource *res, struct d_host_info *host1, struct d_host_info *host2)
{
struct d_volume *a, *b;
/* change the error output, if we have been called to
* compare stacked with lower resource volumes */
int compare_stacked = host1->lower && host1->lower->me == host2;
a = host1->volumes;
b = host2->volumes;
/* volume lists are supposed to be sorted on vnr */
while (a || b) {
while (a && (!b || a->vnr < b->vnr)) {
err("%s:%d: in resource %s, on %s { ... }: volume %d not defined on %s\n",
config_file, line, res->name,
names_to_str(host1->on_hosts),
a->vnr,
compare_stacked ? host1->lower->name
: names_to_str(host2->on_hosts));
a = a->next;
config_valid = 0;
}
while (b && (!a || a->vnr > b->vnr)) {
/* Though unusual, it is "legal" for a lower resource
* to have more volumes than the resource stacked on
* top of it. Warn (if we have a terminal),
* but consider it as valid. */
if (!(compare_stacked && no_tty))
err("%s:%d: in resource %s, on %s { ... }: "
"volume %d missing (present on %s)\n",
config_file, line, res->name,
names_to_str(host1->on_hosts),
b->vnr,
compare_stacked ? host1->lower->name
: names_to_str(host2->on_hosts));
if (!compare_stacked)
config_valid = 0;
b = b->next;
}
if (a && b && a->vnr == b->vnr) {
if (a->implicit != b->implicit) {
err("%s:%d: in resource %s, on %s resp. %s: volume %d must not be implicit on one but not the other\n",
config_file, line, res->name,
names_to_str(host1->on_hosts),
compare_stacked ? host1->lower->name : names_to_str(host2->on_hosts),
a->vnr);
config_valid = 0;
}
a = a->next;
b = b->next;
}
}
}
/* Ensure that in all host sections the same volumes are defined */
void check_volumes_hosts(struct d_resource *res)
{
struct d_host_info *host1, *host2;
host1 = res->all_hosts;
if (!host1)
return;
for (host2 = host1->next; host2; host2 = host2->next)
check_volume_sets_equal(res, host1, host2);
}
enum parse_host_section_flags {
REQUIRE_ALL = 1,
BY_ADDRESS = 2,
};
void parse_host_section(struct d_resource *res,
struct d_name* on_hosts,
enum parse_host_section_flags flags)
{
struct d_host_info *host;
struct d_volume *vol;
struct d_name *h;
int in_braces = 1;
c_section_start = line;
fline = line;
host = calloc(1,sizeof(struct d_host_info));
host->on_hosts = on_hosts;
host->config_line = c_section_start;
if (flags & BY_ADDRESS) {
/* floating <address> {} */
char *fake_uname = NULL;
int token;
host->by_address = 1;
__parse_address(&host->address, &host->port, &host->address_family);
check_uniq("IP", "%s:%s", host->address, host->port);
if (!strcmp(host->address_family, "ipv6"))
m_asprintf(&fake_uname, "ipv6 [%s]:%s", host->address, host->port);
else
m_asprintf(&fake_uname, "%s:%s", host->address, host->port);
on_hosts = names_from_str(fake_uname);
host->on_hosts = on_hosts;
token = yylex();
switch(token) {
case '{':
break;
case ';':
in_braces = 0;
break;
default:
pe_expected_got("{ | ;", token);
}
}
for_each_host(h, on_hosts)
check_upr("host section", "%s: on %s", res->name, h->name);
res->all_hosts = APPEND(res->all_hosts, host);
while (in_braces) {
int token = yylex();
fline = line;
switch (token) {
case TK_DISK:
for_each_host(h, on_hosts)
check_upr("disk statement", "%s:%s:disk", res->name, h->name);
goto vol0stmt;
/* for_each_host(h, on_hosts)
check_uniq("disk", "disk:%s:%s", h->name, yylval.txt); */
case TK_DEVICE:
for_each_host(h, on_hosts)
check_upr("device statement", "%s:%s:device", res->name, h->name);
goto vol0stmt;
case TK_META_DISK:
for_each_host(h, on_hosts)
check_upr("meta-disk statement", "%s:%s:meta-disk", res->name, h->name);
goto vol0stmt;
case TK_FLEX_META_DISK:
for_each_host(h, on_hosts)
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);
}
for_each_host(h, on_hosts)
check_upr("address statement", "%s:%s:address", res->name, h->name);
parse_address(on_hosts, &host->address, &host->port, &host->address_family);
range_check(R_PORT, "port", host->port);
break;
case TK_ALT_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);
}
for_each_host(h, on_hosts)
check_upr("alt-address statement", "%s:%s:alt-address", res->name, h->name);
parse_address(on_hosts, &host->alt_address, &host->alt_port, &host->alt_address_family);
range_check(R_PORT, "port", host->alt_port);
break;
case TK_PROXY:
parse_proxy_section(host);
break;
case TK_VOLUME:
EXP(TK_INTEGER);
host->volumes = INSERT_SORTED(host->volumes,
parse_volume(atoi(yylval.txt), on_hosts),
vnr);
break;
case TK_OPTIONS:
EXP('{');
host->res_options = parse_options(0,
0,
TK_RES_OPTION);
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");
}
}
inherit_volumes(res->volumes, host);
for_each_volume(vol, host->volumes)
check_meta_disk(vol, host);
if (!(flags & REQUIRE_ALL))
return;
if (!host->address)
derror(host, res, "address");
check_volumes_complete(res, host);
}
void parse_skip()
{
int level;
int token;
fline = line;
token = yylex();
switch (token) {
case TK_STRING:
EXP('{');
break;
case '{':
break;
default:
check_string_error(token);
pe_expected("[ some_text ] {");
}
level = 1;
while (level) {
switch (yylex()) {
case '{':
/* if you really want to,
you can wrap this with a GB size config file :) */
level++;
break;
case '}':
level--;
break;
case 0:
err("%s:%u: reached eof while parsing this skip block.\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
}
while (level) ;
}
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));
res->all_hosts = APPEND(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:
/* for_each_host(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:
for_each_host(h, host->on_hosts)
check_upr("address statement", "%s:%s:address", res->name, h->name);
parse_address(NULL, &host->address, &host->port, &host->address_family);
range_check(R_PORT, "port", yylval.txt);
break;
case TK_ALT_ADDRESS:
for_each_host(h, host->on_hosts)
check_upr("alt-address statement", "%s:%s:alt-address", res->name, h->name);
parse_address(NULL, &host->alt_address, &host->alt_port, &host->alt_address_family);
range_check(R_PORT, "port", yylval.txt);
break;
case TK_PROXY:
parse_proxy_section(host);
break;
case TK_VOLUME:
EXP(TK_INTEGER);
host->volumes = INSERT_SORTED(host->volumes, parse_stacked_volume(atoi(yylval.txt)), vnr);
break;
case '}':
goto break_loop;
default:
pe_expected("device | address | proxy");
}
}
break_loop:
res->stacked_on_one = 1;
inherit_volumes(res->volumes, host);
if (!host->address)
derror(host,res,"address");
}
void startup_delegate(void *ctx)
{
struct d_resource *res = (struct d_resource *)ctx;
if (!strcmp(yytext, "become-primary-on")) {
parse_hosts(&res->become_primary_on, ';');
} 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 set_me_in_resource(struct d_resource* res, int match_on_proxy)
{
struct d_host_info *host;
/* Determine the local host section */
for (host = res->all_hosts; host; host=host->next) {
/* do we match this host? */
if (match_on_proxy) {
if (!host->proxy || !name_in_names(nodeinfo.nodename, host->proxy->on_hosts))
continue;
} else if (host->by_address) {
if (!have_ip(host->address_family, host->address) &&
/* for debugging only, e.g. __DRBD_NODE__=10.0.0.1 */
strcmp(nodeinfo.nodename, host->address))
continue;
} else if (host->lower) {
if (!host->lower->me)
continue;
} else if (!host->on_hosts) {
/* huh? a resource without hosts to run on?! */
continue;
} else {
if (!name_in_names(nodeinfo.nodename, host->on_hosts) &&
strcmp("_this_host", host->on_hosts->name))
continue;
}
/* we matched. */
if (res->ignore) {
config_valid = 0;
err("%s:%d: in resource %s, %s %s { ... }:\n"
"\tYou cannot ignore and define at the same time.\n",
res->config_file, host->config_line, res->name,
host->lower ? "stacked-on-top-of" : "on",
host->lower ? host->lower->name : names_to_str(host->on_hosts));
}
if (res->me) {
config_valid = 0;
err("%s:%d: in resource %s, %s %s { ... } ... %s %s { ... }:\n"
"\tThere are multiple host sections for this node.\n",
res->config_file, host->config_line, res->name,
res->me->lower ? "stacked-on-top-of" : "on",
res->me->lower ? res->me->lower->name : names_to_str(res->me->on_hosts),
host->lower ? "stacked-on-top-of" : "on",
host->lower ? host->lower->name : names_to_str(host->on_hosts));
}
res->me = host;
if (host->lower)
res->stacked = 1;
}
/* If there is no me, implicitly ignore that resource */
if (!res->me) {
res->ignore = 1;
return;
}
}
void set_peer_in_resource(struct d_resource* res, int peer_required)
{
struct d_host_info *host = NULL;
if (res->ignore)
return;
/* me must be already set */
if (!res->me) {
/* should have been implicitly ignored. */
err("%s:%d: in resource %s:\n"
"\tcannot determine the peer, don't even know myself!\n",
res->config_file, res->start_line, res->name);
exit(E_THINKO);
}
/* only one host section? */
if (!res->all_hosts->next) {
if (peer_required) {
fprintf(stderr,
"%s:%d: in resource %s:\n"
"\tMissing section 'on <PEER> { ... }'.\n",
res->config_file, res->start_line, res->name);
config_valid = 0;
}
return;
}
/* short cut for exactly two host sections.
* silently ignore any --peer connect_to_host option. */
if (res->all_hosts->next->next == NULL) {
res->peer = res->all_hosts == res->me ?
res->all_hosts->next : res->all_hosts;
if (dry_run > 1 && connect_to_host)
err("%s:%d: in resource %s:\n"
"\tIgnoring --peer '%s': there are only two host sections.\n",
res->config_file, res->start_line, res->name, connect_to_host);
return;
}
/* Multiple peer hosts to choose from.
* we need some help! */
if (!connect_to_host) {
if (peer_required) {
err("%s:%d: in resource %s:\n"
"\tThere are multiple host sections for the peer node.\n"
"\tUse the --peer option to select which peer section to use.\n",
res->config_file, res->start_line, res->name);
config_valid = 0;
}
return;
}
for (host = res->all_hosts; host; host=host->next) {
if (host->by_address && strcmp(connect_to_host, host->address))
continue;
if (host->proxy && !name_in_names(nodeinfo.nodename, host->proxy->on_hosts))
continue;
if (!name_in_names(connect_to_host, host->on_hosts))
continue;
if (host == res->me) {
err("%s:%d: in resource %s\n"
"\tInvoked with --peer '%s', but that matches myself!\n",
res->config_file, res->start_line, res->name, connect_to_host);
res->peer = NULL;
break;
}
if (res->peer) {
err("%s:%d: in resource %s:\n"
"\tInvoked with --peer '%s', but that matches multiple times!\n",
res->config_file, res->start_line, res->name, connect_to_host);
res->peer = NULL;
break;
}
res->peer = host;
}
if (peer_required && !res->peer) {
config_valid = 0;
if (!host)
err("%s:%d: in resource %s:\n"
"\tNo host ('on' or 'floating') section matches --peer '%s'\n",
res->config_file, res->start_line, res->name, connect_to_host);
}
}
void set_on_hosts_in_res(struct d_resource *res)
{
struct d_resource *l_res, *tmp;
struct d_host_info *host, *host2;
struct d_name *h, **last;
for (host = res->all_hosts; host; host=host->next) {
if (host->lower_name) {
for_each_resource(l_res, tmp, config) {
if (!strcmp(l_res->name, host->lower_name))
break;
}
if (l_res == NULL) {
err("%s:%d: in resource %s, "
"referenced resource '%s' not defined.\n",
res->config_file, res->start_line, res->name,
host->lower_name);
config_valid = 0;
continue;
}
/* Simple: host->on_hosts = concat_names(l_res->me->on_hosts, l_res->peer->on_hosts); */
last = NULL;
for (host2 = l_res->all_hosts; host2; host2 = host2->next)
if (!host2->lower_name) {
append_names(&host->on_hosts, &last, host2->on_hosts);
for_each_host(h, host2->on_hosts) {
struct d_volume *vol;
for_each_volume(vol, host->volumes)
check_uniq("device-minor", "device-minor:%s:%u", h->name,
vol->device_minor);
for_each_volume(vol, host->volumes)
if (vol->device)
check_uniq("device", "device:%s:%s", h->name,
vol->device);
}
}
host->lower = l_res;
/* */
if (addr_scope_local(host->address))
for_each_host(h, host->on_hosts)
check_uniq("IP", "%s:%s:%s", h->name, host->address, host->port);
}
}
}
void set_disk_in_res(struct d_resource *res)
{
struct d_host_info *host;
struct d_volume *a, *b;
if (res->ignore)
return;
for (host = res->all_hosts; host; host=host->next) {
if (!host->lower)
continue;
if (host->lower->ignore)
continue;
check_volume_sets_equal(res, host, host->lower->me);
if (!config_valid)
/* don't even bother for broken config. */
continue;
/* volume lists are sorted on vnr */
a = host->volumes;
b = host->lower->me->volumes;
while (a) {
while (b && a->vnr > b->vnr) {
/* Lower resource has more volumes.
* Probably unusual, but we decided
* that it should be legal.
* Skip those that do not match */
b = b->next;
}
if (a && b && a->vnr == b->vnr) {
if (b->device)
m_asprintf(&a->disk, "%s", b->device);
else
m_asprintf(&a->disk, "/dev/drbd%u", b->device_minor);
/* stacked implicit volumes need internal meta data, too */
if (!a->meta_disk)
m_asprintf(&a->meta_disk, "internal");
if (!a->meta_index)
m_asprintf(&a->meta_index, "internal");
a = a->next;
b = b->next;
} else {
/* config_invalid should have been set
* by check_volume_sets_equal */
assert(0);
}
}
}
}
void proxy_delegate(void *ctx)
{
struct d_option **proxy_plugins = (struct d_option **)ctx;
int token;
struct d_option *options, *opt;
struct d_name *line, *word, **pnp;
opt = NULL;
token = yylex();
if (token != '{') {
err("%s:%d: expected \"{\" after \"proxy\" keyword\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
options = NULL;
while (1) {
line = NULL;
pnp = &line;
while (1) {
yylval.txt = NULL;
token = yylex();
if (token <= 0) {
err("%s:%d: Unexpected end-of-file\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
if (token == ';')
break;
if (token == '}') {
if (pnp == &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);
word->next = NULL;
*pnp = word;
pnp = &word->next;
}
opt = calloc(1, sizeof(struct d_option));
if (!opt)
pdperror("out of memory.");
opt->name = strdup(names_to_str(line));
options = APPEND(options, opt);
free_names(line);
}
out:
if (proxy_plugins)
*proxy_plugins = options;
}
static int parse_proxy_options(struct d_option **proxy_options, struct d_option **proxy_plugins)
{
struct d_option *opts;
EXP('{');
opts = parse_options_d(0, 0, TK_PROXY_OPTION | TK_PROXY_GROUP,
TK_PROXY_DELEGATE, proxy_delegate, proxy_plugins);
if (proxy_options)
*proxy_options = opts;
return 0;
}
int parse_proxy_options_section(struct d_resource *res)
{
int token;
struct d_resource dummy_res = { "dummy", };
token = yylex();
if (token != TK_PROXY) {
yyrestart(yyin); /* flushes flex's buffers */
return 1;
}
if (!res)
res = &dummy_res;
return parse_proxy_options(&res->proxy_options, &res->proxy_plugins);
}
struct d_resource* parse_resource(char* res_name, enum pr_flags flags)
{
struct d_resource* res;
struct d_name *host_names;
char *opt_name;
int token;
check_upr_init();
check_uniq("resource section", res_name);
res=calloc(1,sizeof(struct d_resource));
res->name = res_name;
res->config_file = config_save;
res->start_line = line;
while(1) {
token = yylex();
fline = line;
switch(token) {
case TK_NET_OPTION:
if (strcmp(yylval.txt, "protocol"))
goto goto_default;
check_upr("protocol statement","%s: protocol",res->name);
opt_name = yylval.txt;
EXP(TK_STRING);
range_check(R_PROTOCOL, opt_name, yylval.txt);
res->net_options = APPEND(res->net_options, new_opt(opt_name, yylval.txt));
EXP(';');
break;
case TK_ON:
parse_hosts(&host_names, '{');
parse_host_section(res, host_names, REQUIRE_ALL);
break;
case TK_STACKED:
parse_stacked_section(res);
break;
case TK_IGNORE:
if (res->me || res->peer) {
err("%s:%d: in resource %s, "
"'ignore-on' statement must precede any real host section (on ... { ... }).\n",
config_file, line, res->name);
exit(E_CONFIG_INVALID);
}
EXP(TK_STRING);
err("%s:%d: in resource %s, WARN: The 'ignore-on' keyword is deprecated.\n",
config_file, line, res->name);
EXP(';');
break;
case TK__THIS_HOST:
EXP('{');
host_names = names_from_str("_this_host");
parse_host_section(res, host_names, 0);
break;
case TK__REMOTE_HOST:
EXP('{');
host_names = names_from_str("_remote_host");
parse_host_section(res, host_names, 0);
break;
case TK_FLOATING:
parse_host_section(res, NULL, REQUIRE_ALL + BY_ADDRESS);
break;
case TK_DISK:
switch (token=yylex()) {
case TK_STRING:
/* open coded parse_volume_stmt() */
volume0(&res->volumes)->disk = yylval.txt;
EXP(';');
break;
case '{':
check_upr("disk section", "%s:disk", res->name);
res->disk_options =
SPLICE(res->disk_options,
parse_options(TK_DISK_FLAG,
TK_DISK_NO_FLAG,
TK_DISK_OPTION));
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('{');
res->net_options =
SPLICE(res->net_options,
parse_options_d(TK_NET_FLAG,
TK_NET_NO_FLAG,
TK_NET_OPTION,
TK_NET_DELEGATE,
&net_delegate,
(void *)flags));
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_d(TK_STARTUP_FLAG,
0,
TK_STARTUP_OPTION,
TK_STARTUP_DELEGATE,
&startup_delegate,
res);
break;
case TK_HANDLER:
check_upr("handlers section", "%s:handlers", res->name);
EXP('{');
res->handlers = parse_options(0, 0, TK_HANDLER_OPTION);
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);
res->volumes = INSERT_SORTED(res->volumes,
parse_volume(atoi(yylval.txt), NULL),
vnr);
break;
case TK_OPTIONS:
check_upr("resource options section", "%s:res_options", res->name);
EXP('{');
res->res_options =
SPLICE(res->res_options,
parse_options(0,
0,
TK_RES_OPTION));
break;
case '}':
case 0:
goto exit_loop;
default:
goto_default:
pe_expected_got("protocol | on | disk | net | syncer |"
" startup | handlers |"
" ignore-on | stacked-on-top-of",token);
}
}
exit_loop:
if (flags == NoneHAllowed && 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);
}
if (!(flags & PARSE_FOR_ADJUST))
check_volumes_hosts(res);
return res;
}
struct d_resource* parse_resource_for_adjust(struct cfg_ctx *ctx)
{
int token;
token = yylex();
if (token != TK_RESOURCE)
return NULL;
token = yylex();
if (token != TK_STRING)
return NULL;
/* FIXME assert that string and ctx->res->name match? */
token = yylex();
if (token != '{')
return NULL;
return parse_resource(ctx->res->name, PARSE_FOR_ADJUST);
}
void post_parse(struct d_resource *config, enum pp_flags flags)
{
struct d_resource *res,*tmp;
for_each_resource(res, tmp, config)
if (res->stacked_on_one)
set_on_hosts_in_res(res); /* sets on_hosts and host->lower */
/* Needs "on_hosts" and host->lower already set */
for_each_resource(res, tmp, config)
if (!res->stacked_on_one)
set_me_in_resource(res, flags & MATCH_ON_PROXY);
/* Needs host->lower->me already set */
for_each_resource(res, tmp, config)
if (res->stacked_on_one)
set_me_in_resource(res, flags & MATCH_ON_PROXY);
// Needs "me" set already
for_each_resource(res, tmp, config)
if (res->stacked_on_one)
set_disk_in_res(res);
}
/* Returns the "previous" count, ie. 0 if this file wasn't seen before. */
int was_file_already_seen(char *fn)
{
ENTRY e, *ep;
char *real_path;
real_path = realpath(fn, NULL);
if (!real_path)
real_path = fn;
ep = NULL;
e.key = real_path;
e.data = real_path;
hsearch_r(e, FIND, &ep, &global_htable);
if (ep) {
/* Can be freed, it's just a queried key. */
if (real_path != fn)
free(real_path);
return 1;
}
e.key = real_path;
e.data = real_path;
hsearch_r(e, ENTER, &ep, &global_htable);
if (!ep) {
err("hash table entry (%s => %s) failed\n", e.key, (char *)e.data);
exit(E_THINKO);
}
/* Must not be freed, because it's still referenced by the hash table. */
/* free(real_path); */
return 0;
}
void include_stmt(char *str)
{
char *last_slash, *tmp;
glob_t glob_buf;
int cwd_fd;
FILE *f;
size_t i;
int r;
/* in order to allow relative paths in include statements we change
directory to the location of the current configuration file. */
cwd_fd = open(".", O_RDONLY | O_CLOEXEC);
if (cwd_fd < 0) {
err("open(\".\") failed: %m\n");
exit(E_USAGE);
}
tmp = strdupa(config_save);
last_slash = strrchr(tmp, '/');
if (last_slash)
*last_slash = 0;
if (chdir(tmp)) {
err("chdir(\"%s\") failed: %m\n", tmp);
exit(E_USAGE);
}
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_file, 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_file, line, str);
config_valid = 0;
}
} else {
err("glob() failed: %d\n", r);
exit(E_USAGE);
}
if (fchdir(cwd_fd) < 0) {
err("fchdir() failed: %m\n");
exit(E_USAGE);
}
close(cwd_fd);
}
void my_parse(void)
{
static int global_htable_init = 0;
if (!global_htable_init) {
check_uniq_init();
global_htable_init = 1;
}
/* 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",NoneHAllowed);
break;
case TK_RESOURCE:
EXP(TK_STRING);
ensure_sanity_of_res_name(yylval.txt);
EXP('{');
config = APPEND(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_htable, 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_htable, what, fmt, ap);
va_end(ap);
return rv;
}