blob: b855e7390094462e91e9f5438c8aab5888e3efab [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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glob.h>
#include <search.h>
#include <fcntl.h>
#include <unistd.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;
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)
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) {
fprintf(stderr,
"%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) {
fprintf(stderr,
"%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,
const char *value)
{
switch (what) {
case R_NO_CHECK:
break;
default:
fprintf(stderr, "%s:%d: unknown range for %s => %s\n",
config_file, fline, name, value);
break;
case R_MINOR_COUNT:
m_strtoll_range(value, 1, name,
DRBD_MINOR_COUNT_MIN, DRBD_MINOR_COUNT_MAX);
break;
case R_DIALOG_REFRESH:
m_strtoll_range(value, 1, name,
DRBD_DIALOG_REFRESH_MIN,
DRBD_DIALOG_REFRESH_MAX);
break;
case R_DISK_SIZE:
m_strtoll_range(value, 's', name,
DRBD_DISK_SIZE_SECT_MIN,
DRBD_DISK_SIZE_SECT_MAX);
break;
case R_TIMEOUT:
m_strtoll_range(value, 1, name, DRBD_TIMEOUT_MIN,
DRBD_TIMEOUT_MAX);
break;
case R_CONNECT_INT:
m_strtoll_range(value, 1, name, DRBD_CONNECT_INT_MIN,
DRBD_CONNECT_INT_MAX);
break;
case R_PING_INT:
m_strtoll_range(value, 1, name, DRBD_PING_INT_MIN,
DRBD_PING_INT_MAX);
break;
case R_MAX_BUFFERS:
m_strtoll_range(value, 1, name, DRBD_MAX_BUFFERS_MIN,
DRBD_MAX_BUFFERS_MAX);
break;
case R_MAX_EPOCH_SIZE:
m_strtoll_range(value, 1, name, DRBD_MAX_EPOCH_SIZE_MIN,
DRBD_MAX_EPOCH_SIZE_MAX);
break;
case R_SNDBUF_SIZE:
m_strtoll_range(value, 1, name, DRBD_SNDBUF_SIZE_MIN,
DRBD_SNDBUF_SIZE_MAX);
break;
case R_RCVBUF_SIZE:
m_strtoll_range(value, 1, name, DRBD_RCVBUF_SIZE_MIN,
DRBD_RCVBUF_SIZE_MAX);
break;
case R_KO_COUNT:
m_strtoll_range(value, 1, name, DRBD_KO_COUNT_MIN,
DRBD_KO_COUNT_MAX);
break;
case R_RATE:
m_strtoll_range(value, 'K', name, DRBD_RATE_MIN, DRBD_RATE_MAX);
break;
case R_AL_EXTENTS:
m_strtoll_range(value, 1, name, DRBD_AL_EXTENTS_MIN,
DRBD_AL_EXTENTS_MAX);
break;
case R_PORT:
m_strtoll_range(value, 1, name, DRBD_PORT_MIN, DRBD_PORT_MAX);
break;
/* FIXME not yet implemented!
case R_META_IDX:
m_strtoll_range(value, 1, name, DRBD_META_IDX_MIN, DRBD_META_IDX_MAX);
break;
*/
case R_WFC_TIMEOUT:
m_strtoll_range(value, 1, name, DRBD_WFC_TIMEOUT_MIN,
DRBD_WFC_TIMEOUT_MAX);
break;
case R_DEGR_WFC_TIMEOUT:
m_strtoll_range(value, 1, name, DRBD_DEGR_WFC_TIMEOUT_MIN,
DRBD_DEGR_WFC_TIMEOUT_MAX);
break;
case R_OUTDATED_WFC_TIMEOUT:
m_strtoll_range(value, 1, name, DRBD_OUTDATED_WFC_TIMEOUT_MIN,
DRBD_OUTDATED_WFC_TIMEOUT_MAX);
break;
case R_C_PLAN_AHEAD:
m_strtoll_range(value, 1, name, DRBD_C_PLAN_AHEAD_MIN,
DRBD_C_PLAN_AHEAD_MAX);
break;
case R_C_DELAY_TARGET:
m_strtoll_range(value, 1, name, DRBD_C_DELAY_TARGET_MIN,
DRBD_C_DELAY_TARGET_MAX);
break;
case R_C_FILL_TARGET:
m_strtoll_range(value, 's', name, DRBD_C_FILL_TARGET_MIN,
DRBD_C_FILL_TARGET_MAX);
break;
case R_C_MAX_RATE:
m_strtoll_range(value, 'k', name, DRBD_C_MAX_RATE_MIN,
DRBD_C_MAX_RATE_MAX);
break;
case R_C_MIN_RATE:
m_strtoll_range(value, 'k', name, DRBD_C_MIN_RATE_MIN,
DRBD_C_MIN_RATE_MAX);
break;
case R_CONG_FILL:
m_strtoll_range(value, 's', name, DRBD_CONG_FILL_MIN,
DRBD_CONG_FILL_MAX);
break;
case R_CONG_EXTENTS:
m_strtoll_range(value, 1, name, DRBD_CONG_EXTENTS_MIN,
DRBD_CONG_EXTENTS_MAX);
break;
}
}
struct d_option *new_opt(char *name, char *value)
{
struct d_option *cn = malloc(sizeof(struct d_option));
/* fprintf(stderr,"%s:%d: %s = %s\n",config_file,line,name,value); */
cn->name = name;
cn->value = value;
cn->mentioned = 0;
cn->is_default = 0;
cn->is_escaped = 0;
return cn;
}
static void derror(struct d_host_info *host, struct d_resource *res, char *text)
{
config_valid = 0;
fprintf(stderr, "%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;
fprintf(stderr, "%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;
fprintf(stderr, "%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.
*/
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)) {
fprintf(stderr, "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)) {
fprintf(stderr, "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) {
perror("vasprintf");
exit(E_THINKO);
}
if (EXIT_ON_CONFLICT && !what) {
fprintf(stderr, "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);
//fprintf(stderr, "FIND %s: %p\n", e.key, ep);
if (ep) {
if (what) {
fprintf(stderr,
"%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 {
//fprintf(stderr, "ENTER %s\t=>\t%s\n", e.key, (char *)e.data);
hsearch_r(e, ENTER, &ep, ht);
if (!ep) {
fprintf(stderr, "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_host_info *host)
{
struct d_name *h;
if (strcmp(host->meta_disk, "internal") != 0) {
/* external */
if (host->meta_index == NULL) {
fprintf(stderr,
"%s:%d: expected 'meta-disk = %s [index]'.\n",
config_file, fline, host->meta_disk);
}
/* index either some number, or "flexible" */
for_each_host(h, host->on_hosts)
check_uniq("meta-disk", "%s:%s[%s]", h->name, host->meta_disk, host->meta_index);
} else if (host->meta_index) {
/* internal */
if (strcmp(host->meta_index, "flexible") != 0) {
/* internal, not flexible, but index given: no sir! */
fprintf(stderr,
"%s:%d: no index allowed with 'meta-disk = internal'.\n",
config_file, fline);
} /* else internal, flexible: fine */
} else {
/* internal, not flexible */
host->meta_index = strdup("internal");
}
}
static void pe_expected(const char *exp)
{
const char *s = yytext;
fprintf(stderr, "%s:%u: Parse error: '%s' expected,\n\t"
"but 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;
}
fprintf(stderr,"%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];
}
fprintf(stderr, "%s:%u: Parse error: '%s' expected,\n\t"
"but 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) {
fprintf(stderr,
"%s:%u: You should put the global {} section\n\t"
"in front of any resource {} section\n",
config_file, line);
}
EXP('{');
while (1) {
int token = yylex();
fline = line;
switch (token) {
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_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_option)
{
if (token_option == TK_HANDLER_OPTION) {
if (!strcmp(*name, "outdate-peer")) {
/* fprintf(stder, "config file:line: name is deprecated ...\n") */
free(*name);
*name = strdup("fence-peer");
}
}
}
static struct d_option *parse_options_d(int token_switch, 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, *ro = 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;
if (token == token_switch) {
options = APPEND(options, new_opt(yylval.txt, NULL));
} else if (token == token_option ||
GET_TOKEN_GROUP(token_option & token_group)) {
opt_name = yylval.txt;
check_and_change_deprecated_alias(&opt_name, token_option);
rc = yylval.rc;
expect_STRING_or_INT();
range_check(rc, opt_name, yylval.txt);
ro = new_opt(opt_name, yylval.txt);
options = APPEND(options, ro);
} else if (token == token_delegate ||
GET_TOKEN_GROUP(token_delegate & token_group)) {
delegate(ctx);
continue;
} else if (token == TK_DEPRECATED_OPTION) {
/* fprintf(stderr, "Warn: Ignoring deprecated option '%s'\n", yylval.txt); */
expect_STRING_or_INT();
} else if (token == '}') {
return options;
} else {
pe_expected("an option keyword");
}
switch (yylex()) {
case TK__IS_DEFAULT:
ro->is_default = 1;
EXP(';');
break;
case ';':
break;
default:
pe_expected("_is_default | ;");
}
}
}
static struct d_option *parse_options(int token_switch, int token_option)
{
return parse_options_d(token_switch, 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 '}':
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;
}
static void parse_meta_disk(char **disk, char** index)
{
EXP(TK_STRING);
*disk = yylval.txt;
if (strcmp("internal", yylval.txt)) {
EXP('[');
EXP(TK_INTEGER);
*index = yylval.txt;
EXP(']');
EXP(';');
} else {
EXP(';');
}
}
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;
fprintf(stderr,
"%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;
fprintf(stderr,
"%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, unsigned *minor, char **device)
{
struct d_name *h;
int m;
switch (yylex()) {
case TK_STRING:
if (!strncmp("drbd", yylval.txt, 4)) {
m_asprintf(device, "/dev/%s", yylval.txt);
free(yylval.txt);
} else
*device = yylval.txt;
if (strncmp("/dev/drbd", *device, 9)) {
fprintf(stderr,
"%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(*device);
if (m < 0) {
fprintf(stderr,
"%s:%d: no minor given nor device name contains a minor number\n",
config_file, fline);
config_valid = 0;
}
*minor = m;
goto out;
case TK_MINOR:
; /* double fall through */
}
case TK_MINOR:
EXP(TK_INTEGER);
*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(*device, *minor);
}
out:
for_each_host(h, on_hosts) {
check_uniq("device-minor", "device-minor:%s:%u", h->name, *minor);
if (*device)
check_uniq("device", "device:%s:%s", h->name, *device);
}
}
enum parse_host_section_flags {
REQUIRE_ALL = 1,
BY_ADDRESS = 2,
};
static 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_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;
host->device_minor = -1;
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);
EXP(TK_STRING);
host->disk = yylval.txt;
for_each_host(h, on_hosts)
check_uniq("disk", "disk:%s:%s", h->name, yylval.txt);
EXP(';');
break;
case TK_DEVICE:
for_each_host(h, on_hosts)
check_upr("device statement", "%s:%s:device", res->name, h->name);
parse_device(on_hosts, &host->device_minor, &host->device);
break;
case TK_ADDRESS:
if (host->by_address) {
fprintf(stderr,
"%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_META_DISK:
for_each_host(h, on_hosts)
check_upr("meta-disk statement", "%s:%s:meta-disk", res->name, h->name);
parse_meta_disk(&host->meta_disk, &host->meta_index);
check_meta_disk(host);
break;
case TK_FLEX_META_DISK:
for_each_host(h, on_hosts)
check_upr("meta-disk statement", "%s:%s:meta-disk", res->name, h->name);
EXP(TK_STRING);
host->meta_disk = yylval.txt;
if (strcmp("internal", yylval.txt)) {
host->meta_index = strdup("flexible");
}
check_meta_disk(host);
EXP(';');
break;
case TK_PROXY:
parse_proxy_section(host);
break;
case '}':
in_braces = 0;
break;
default:
pe_expected("disk | device | address | meta-disk "
"| flexible-meta-disk");
}
}
/* Inherit device, disk, meta_disk and meta_index from the resource. */
if(!host->disk && res->disk) {
host->disk = strdup(res->disk);
for_each_host(h, on_hosts)
check_uniq("disk", "disk:%s:%s", h->name, host->disk);
}
if(!host->device && res->device) {
host->device = strdup(res->device);
}
if (host->device_minor == -1U && res->device_minor != -1U) {
host->device_minor = res->device_minor;
for_each_host(h, on_hosts)
check_uniq("device-minor", "device-minor:%s:%d", h->name, host->device_minor);
}
if(!host->meta_disk && res->meta_disk) {
host->meta_disk = strdup(res->meta_disk);
if(res->meta_index) host->meta_index = strdup(res->meta_index);
check_meta_disk(host);
}
if (!(flags & REQUIRE_ALL))
return;
if (!host->device && host->device_minor == -1U)
derror(host, res, "device");
if (!host->disk)
derror(host, res, "disk");
if (!host->address)
derror(host, res, "address");
if (!host->meta_disk)
derror(host, res, "meta-disk");
}
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:
fprintf(stderr, "%s:%u: reached eof "
"while parsing this skip block.\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
}
while (level) ;
}
static 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));
host->device_minor = -1;
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;
m_asprintf(&host->meta_disk, "%s", "internal");
m_asprintf(&host->meta_index, "%s", "internal");
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, &host->device_minor, &host->device);
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_PROXY:
parse_proxy_section(host);
break;
case '}':
goto break_loop;
default:
pe_expected("device | address | proxy");
}
}
break_loop:
res->stacked_on_one = 1;
/* inherit device */
if (!host->device && res->device) {
host->device = strdup(res->device);
for_each_host(h, host->on_hosts) {
if (host->device)
check_uniq("device", "device:%s:%s", h->name, host->device);
}
}
if (host->device_minor == -1U && res->device_minor != -1U) {
host->device_minor = res->device_minor;
for_each_host(h, host->on_hosts)
check_uniq("device-minor", "device-minor:%s:%d", h->name, host->device_minor);
}
if (!host->device && host->device_minor == -1U)
derror(host, res, "device");
if (!host->address)
derror(host,res,"address");
if (!host->meta_disk)
derror(host,res,"meta-disk");
}
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 & IgnDiscardMyData)
EXP(';');
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;
fprintf(stderr,
"%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;
fprintf(stderr,
"%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. */
fprintf(stderr, "%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)
fprintf(stderr,
"%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) {
fprintf(stderr,
"%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) {
fprintf(stderr,
"%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) {
fprintf(stderr,
"%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)
fprintf(stderr,
"%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) {
fprintf(stderr, "%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) {
check_uniq("device-minor", "device-minor:%s:%u", h->name,
host->device_minor);
if (host->device)
check_uniq("device", "device:%s:%s", h->name,
host->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;
if (res->ignore)
return;
for (host = res->all_hosts; host; host=host->next) {
if (host->lower) {
if (res->stacked && host->lower->stacked) {
fprintf(stderr,
"%s:%d: in resource %s, stacked-on-top-of %s { ... }:\n"
"\tFIXME. I won't stack stacked resources.\n",
res->config_file, res->start_line, res->name, host->lower_name);
config_valid = 0;
}
if (host->lower->ignore)
continue;
if (host->lower->me->device)
m_asprintf(&host->disk, "%s", host->lower->me->device);
else
m_asprintf(&host->disk, "/dev/drbd%u", host->lower->me->device_minor);
if (!host->disk)
derror(host,res,"disk");
}
}
}
void proxy_delegate(void *ctx)
{
struct d_resource *res = (struct d_resource *)ctx;
int token;
struct d_option *options, *opt;
struct d_name *line, *word, **pnp;
opt = NULL;
token = yylex();
if (token != '{') {
fprintf(stderr, "%s:%d: expected \"{\" after \"proxy\" keyword\n",
config_file, fline);
exit(E_CONFIG_INVALID);
}
options = NULL;
while (1) {
pnp = &line;
while (1) {
token = yylex();
if (token == ';')
break;
if (token == '}') {
if (pnp == &line)
goto out;
fprintf(stderr, "%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;
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:
res->proxy_plugins = options;
}
int parse_proxy_settings(struct d_resource *res, int flags)
{
int token;
if (flags & PARSER_CHECK_PROXY_KEYWORD) {
token = yylex();
if (token != TK_PROXY) {
if (flags & PARSER_STOP_IF_INVALID) {
yyrestart(yyin); /* flushes flex's buffers */
return 1;
}
pe_expected_got("proxy", token);
}
}
EXP('{');
res->proxy_options =
parse_options_d(TK_PROXY_SWITCH,
TK_PROXY_OPTION | TK_PROXY_GROUP,
TK_PROXY_DELEGATE,
proxy_delegate, res);
return 0;
}
struct d_resource* parse_resource(char* res_name, enum pr_flags flags)
{
struct d_resource* res;
struct d_name *host_names;
int token;
check_upr_init();
check_uniq("resource section", res_name);
res=calloc(1,sizeof(struct d_resource));
res->name = res_name;
res->device_minor = -1;
res->config_file = config_file;
res->start_line = line;
while(1) {
token = yylex();
fline = line;
switch(token) {
case TK_PROTOCOL:
check_upr("protocol statement","%s: protocol",res->name);
EXP(TK_STRING);
res->protocol=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) {
fprintf(stderr,
"%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);
fprintf(stderr, "%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:
res->disk = yylval.txt;
EXP(';');
break;
case '{':
check_upr("disk section", "%s:disk", res->name);
res->disk_options = parse_options(TK_DISK_SWITCH,
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 = parse_options_d(TK_NET_SWITCH,
TK_NET_OPTION,
TK_NET_DELEGATE,
&net_delegate,
(void *)flags);
break;
case TK_SYNCER:
check_upr("syncer section", "%s:syncer", res->name);
EXP('{');
res->sync_options = parse_options(TK_SYNCER_SWITCH,
TK_SYNCER_OPTION);
break;
case TK_STARTUP:
check_upr("startup section", "%s:startup", res->name);
EXP('{');
res->startup_options=parse_options_d(TK_STARTUP_SWITCH,
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, TK_HANDLER_OPTION);
break;
case TK_PROXY:
check_upr("proxy section", "%s:proxy", res->name);
parse_proxy_settings(res, 0);
break;
case TK_DEVICE:
check_upr("device statement", "%s:device", res->name);
parse_device(NULL, &res->device_minor, &res->device);
break;
case TK_META_DISK:
parse_meta_disk(&res->meta_disk, &res->meta_index);
break;
case TK_FLEX_META_DISK:
EXP(TK_STRING);
res->meta_disk = yylval.txt;
if (strcmp("internal", yylval.txt)) {
res->meta_index = strdup("flexible");
}
EXP(';');
break;
case '}':
case 0:
goto exit_loop;
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;
fprintf(stderr,
"%s:%d: in the %s section, there are no host sections"
" allowed.\n",
config_file, c_section_start, res->name);
}
return res;
}
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);
}
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) {
fprintf(stderr, "open(\".\") failed: %m\n");
exit(E_USAGE);
}
tmp = strdupa(config_save);
last_slash = strrchr(tmp, '/');
if (last_slash)
*last_slash = 0;
if (chdir(tmp)) {
fprintf(stderr, "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++) {
f = fopen(glob_buf.gl_pathv[i], "re");
if (f) {
include_file(f, strdup(glob_buf.gl_pathv[i]));
fclose(f);
} else {
fprintf(stderr,
"%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, '[')) {
fprintf(stderr,
"%s:%d: Failed to open include file '%s'.\n",
config_file, line, str);
config_valid = 0;
}
} else {
fprintf(stderr, "glob() failed: %d\n", r);
exit(E_USAGE);
}
if (fchdir(cwd_fd) < 0) {
fprintf(stderr, "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;
}
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;
}