| /* |
| * |
| 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; |
| } |