blob: 0b1f376e1b90d381d9889fceca92044a4b478796 [file] [log] [blame]
/*
drbdadm_main.c
This file is part of DRBD by Philipp Reisner and Lars Ellenberg.
Copyright (C) 2002-2008, LINBIT Information Technologies GmbH.
Copyright (C) 2002-2008, Philipp Reisner <philipp.reisner@linbit.com>.
Copyright (C) 2002-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 <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <search.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <time.h>
#include "linux/drbd_limits.h"
#include "drbdtool_common.h"
#include "drbdadm.h"
#include "registry.h"
#include "config_flags.h"
#include "shared_main.h"
#define MAX_ARGS 40
static int indent = 0;
#define INDENT_WIDTH 4
#define BFMT "%s;\n"
#define IPV4FMT "%-16s %s %s:%s;\n"
#define IPV6FMT "%-16s %s [%s]:%s;\n"
#define MDISK "%-16s %s;\n"
#define MDISKI "%-16s %s [%s];\n"
#define printI(fmt, args... ) printf("%*s" fmt,INDENT_WIDTH * indent,"" , ## args )
#define printA(name, val ) \
printf("%*s%*s %3s;\n", \
INDENT_WIDTH * indent,"" , \
-24+INDENT_WIDTH * indent, \
name, val )
char *progname;
struct adm_cmd {
const char *name;
int (*function) (struct cfg_ctx *);
/* which level this command is for.
* 0: don't show this command, ever
* 1: normal administrative commands, shown in normal help
* 2-4: shown on "drbdadm hidden-commands"
* 2: useful for shell scripts
* 3: callbacks potentially called from kernel module on certain events
* 4: advanced, experts and developers only */
unsigned int show_in_usage:3;
/* if set, command requires an explicit resource name */
unsigned int res_name_required:1;
/* if set, command requires an explicit volume number as well */
unsigned int vol_id_required:1;
/* most commands need to iterate over all volumes in the resource */
unsigned int iterate_volumes:1;
/* error out if the ip specified is not available/active now */
unsigned int verify_ips:1;
/* if set, use the "cache" in /var/lib/drbd to figure out
* which config file to use.
* This is necessary for handlers (callbacks from kernel) to work
* when using "drbdadm -c /some/other/config/file" */
unsigned int use_cached_config_file:1;
unsigned int need_peer:1;
unsigned int is_proxy_cmd:1;
unsigned int uc_dialog:1; /* May show usage count dialog */
unsigned int test_config:1; /* Allow -t option */
const struct context_def *drbdsetup_ctx;
};
struct deferred_cmd {
int (*function) (struct cfg_ctx *);
struct cfg_ctx ctx;
struct deferred_cmd *next;
};
struct option general_admopt[] = {
{"stacked", no_argument, 0, 'S'},
{"dry-run", no_argument, 0, 'd'},
{"verbose", no_argument, 0, 'v'},
{"config-file", required_argument, 0, 'c'},
{"config-to-test", required_argument, 0, 't'},
{"drbdsetup", required_argument, 0, 's'},
{"drbdmeta", required_argument, 0, 'm'},
{"drbd-proxy-ctl", required_argument, 0, 'p'},
{"sh-varname", required_argument, 0, 'n'},
{"peer", required_argument, 0, 'P'},
{"version", no_argument, 0, 'V'},
{"setup-option", required_argument, 0, 'W'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
struct option *admopt = general_admopt;
extern void my_parse();
extern int yydebug;
extern FILE *yyin;
static int adm_generic_l(struct cfg_ctx *);
static int adm_up(struct cfg_ctx *);
static int adm_status(struct cfg_ctx *);
static int adm_dump(struct cfg_ctx *);
static int adm_dump_xml(struct cfg_ctx *);
static int adm_wait_c(struct cfg_ctx *);
static int adm_wait_ci(struct cfg_ctx *);
static int adm_proxy_up(struct cfg_ctx *);
static int adm_proxy_down(struct cfg_ctx *);
static int sh_nop(struct cfg_ctx *);
static int sh_resources(struct cfg_ctx *);
static int sh_resource(struct cfg_ctx *);
static int sh_mod_parms(struct cfg_ctx *);
static int sh_dev(struct cfg_ctx *);
static int sh_udev(struct cfg_ctx *);
static int sh_minor(struct cfg_ctx *);
static int sh_ip(struct cfg_ctx *);
static int sh_lres(struct cfg_ctx *);
static int sh_ll_dev(struct cfg_ctx *);
static int sh_md_dev(struct cfg_ctx *);
static int sh_md_idx(struct cfg_ctx *);
static int sh_b_pri(struct cfg_ctx *);
static int sh_status(struct cfg_ctx *);
static int admm_generic(struct cfg_ctx *);
static int adm_khelper(struct cfg_ctx *);
static int adm_generic_b(struct cfg_ctx *);
static int hidden_cmds(struct cfg_ctx *);
static int adm_outdate(struct cfg_ctx *);
static int adm_chk_resize(struct cfg_ctx *);
static void dump_options(char *name, struct d_option *opts);
struct d_volume *volume_by_vnr(struct d_volume *volumes, int vnr);
struct d_resource *res_by_name(const char *name);
int ctx_by_name(struct cfg_ctx *ctx, const char *id);
int ctx_set_implicit_volume(struct cfg_ctx *ctx);
static char *get_opt_val(struct d_option *, const char *, char *);
char ss_buffer[1024];
struct utsname nodeinfo;
int line = 1;
int fline;
char *config_file = NULL;
char *config_save = NULL;
char *config_test = NULL;
struct d_resource *config = NULL;
struct d_resource *common = NULL;
struct ifreq *ifreq_list = NULL;
int is_drbd_top;
enum { NORMAL, STACKED, IGNORED, __N_RESOURCE_TYPES };
int nr_resources[__N_RESOURCE_TYPES];
int nr_volumes[__N_RESOURCE_TYPES];
int number_of_minors = 0;
int config_from_stdin = 0;
int config_valid = 1;
int no_tty;
int dry_run = 0;
int verbose = 0;
int adjust_with_progress = 0;
bool help;
int do_verify_ips = 0;
int do_register = 1;
/* whether drbdadm was called with "all" instead of resource name(s) */
int all_resources = 0;
char *drbdsetup = NULL;
char *drbdmeta = NULL;
char *drbdadm_83 = NULL;
char *drbd_proxy_ctl;
char *sh_varname = NULL;
struct setup_option *setup_options;
char *connect_to_host = NULL;
struct deferred_cmd *deferred_cmds[__CFG_LAST] = { NULL, };
struct deferred_cmd *deferred_cmds_tail[__CFG_LAST] = { NULL, };
void add_setup_option(bool explicit, char *option)
{
int n = 0;
if (setup_options) {
while (setup_options[n].option)
n++;
}
setup_options = realloc(setup_options, (n + 2) * sizeof(*setup_options));
if (!setup_options) {
/* ... */
}
setup_options[n].explicit = explicit;
setup_options[n].option = option;
n++;
setup_options[n].option = NULL;
}
int adm_adjust_wp(struct cfg_ctx *ctx)
{
if (!verbose && !dry_run)
adjust_with_progress = 1;
return adm_adjust(ctx);
}
/* DRBD adm_cmd flags shortcuts,
* to avoid merge conflicts and unreadable diffs
* when we add the next flag */
#define DRBD_acf1_default \
.show_in_usage = 1, \
.res_name_required = 1, \
.iterate_volumes = 1, \
.verify_ips = 0, \
.uc_dialog = 1, \
#define DRBD_acf1_resname \
.show_in_usage = 1, \
.res_name_required = 1, \
.uc_dialog = 1, \
#define DRBD_acf1_connect \
.show_in_usage = 1, \
.res_name_required = 1, \
.iterate_volumes = 0, \
.verify_ips = 1, \
.need_peer = 1, \
.uc_dialog = 1, \
#define DRBD_acf1_up \
.show_in_usage = 1, \
.res_name_required = 1, \
.iterate_volumes = 1, \
.verify_ips = 1, \
.need_peer = 1, \
.uc_dialog = 1, \
#define DRBD_acf1_defnet \
.show_in_usage = 1, \
.res_name_required = 1, \
.iterate_volumes = 1, \
.verify_ips = 1, \
.uc_dialog = 1, \
#define DRBD_acf3_handler \
.show_in_usage = 3, \
.res_name_required = 1, \
.iterate_volumes = 0, \
.vol_id_required = 1, \
.verify_ips = 0, \
.use_cached_config_file = 1, \
#define DRBD_acf3_res_handler \
.show_in_usage = 3, \
.res_name_required = 1, \
.iterate_volumes = 0, \
.vol_id_required = 0, \
.verify_ips = 0, \
.use_cached_config_file = 1, \
#define DRBD_acf4_advanced \
.show_in_usage = 4, \
.res_name_required = 1, \
.iterate_volumes = 1, \
.verify_ips = 0, \
.uc_dialog = 1, \
#define DRBD_acf4_advanced_need_vol \
.show_in_usage = 4, \
.res_name_required = 1, \
.iterate_volumes = 1, \
.vol_id_required = 1, \
.verify_ips = 0, \
.uc_dialog = 1, \
#define DRBD_acf1_dump \
.show_in_usage = 1, \
.res_name_required = 1, \
.verify_ips = 1, \
.uc_dialog = 1, \
.test_config = 1, \
#define DRBD_acf2_shell \
.show_in_usage = 2, \
.iterate_volumes = 1, \
.res_name_required = 1, \
.verify_ips = 0, \
#define DRBD_acf2_sh_resname \
.show_in_usage = 2, \
.iterate_volumes = 0, \
.res_name_required = 1, \
.verify_ips = 0, \
#define DRBD_acf2_proxy \
.show_in_usage = 2, \
.res_name_required = 1, \
.verify_ips = 0, \
.need_peer = 1, \
.is_proxy_cmd = 1, \
#define DRBD_acf2_hook \
.show_in_usage = 2, \
.res_name_required = 1, \
.verify_ips = 0, \
.use_cached_config_file = 1, \
#define DRBD_acf2_gen_shell \
.show_in_usage = 2, \
.res_name_required = 0, \
.verify_ips = 0, \
struct adm_cmd cmds[] = {
/* name, function, flags
* sort order:
* - normal config commands,
* - normal meta data manipulation
* - sh-*
* - handler
* - advanced
***/
{"attach", adm_attach, DRBD_acf1_default
.drbdsetup_ctx = &attach_cmd_ctx, },
{"status", adm_status, DRBD_acf1_resname}, /* keep as its own, we switch on it in main(), don't directly use amd_generic_s */
{"disk-options", adm_disk_options, DRBD_acf1_default
.drbdsetup_ctx = &disk_options_ctx, },
{"detach", adm_generic_l, DRBD_acf1_default
.drbdsetup_ctx = &detach_cmd_ctx, },
{"connect", adm_connect, DRBD_acf1_connect
.drbdsetup_ctx = &connect_cmd_ctx, },
{"net-options", adm_net_options, DRBD_acf1_connect
.drbdsetup_ctx = &net_options_ctx, },
{"disconnect", adm_disconnect, DRBD_acf1_resname
.drbdsetup_ctx = &disconnect_cmd_ctx, },
{"up", adm_up, DRBD_acf1_up},
{"resource-options", adm_res_options, DRBD_acf1_resname
.drbdsetup_ctx = &resource_options_cmd_ctx, },
{"down", adm_generic_l, DRBD_acf1_resname},
{"primary", adm_generic_l, DRBD_acf1_default
.drbdsetup_ctx = &primary_cmd_ctx, },
{"secondary", adm_generic_l, DRBD_acf1_default},
{"invalidate", adm_generic_b, DRBD_acf1_default},
{"invalidate-remote", adm_generic_l, DRBD_acf1_defnet},
{"outdate", adm_outdate, DRBD_acf1_default},
{"resize", adm_resize, DRBD_acf1_defnet},
{"verify", adm_generic_s, DRBD_acf1_defnet},
{"pause-sync", adm_generic_s, DRBD_acf1_defnet},
{"resume-sync", adm_generic_s, DRBD_acf1_defnet},
{"adjust", adm_adjust, DRBD_acf1_connect},
{"adjust-with-progress", adm_adjust_wp, DRBD_acf1_connect},
{"wait-connect", adm_wait_c, DRBD_acf1_defnet},
{"wait-con-int", adm_wait_ci,
.show_in_usage = 1,.verify_ips = 1,},
{"role", adm_generic_s, DRBD_acf1_default},
{"cstate", adm_generic_s, DRBD_acf1_default},
{"dstate", adm_generic_b, DRBD_acf1_default},
{"dump", adm_dump, DRBD_acf1_dump},
{"dump-xml", adm_dump_xml, DRBD_acf1_dump},
{"create-md", adm_create_md, DRBD_acf1_default},
{"show-gi", adm_generic_b, DRBD_acf1_default},
{"get-gi", adm_generic_b, DRBD_acf1_default},
{"dump-md", admm_generic, DRBD_acf1_default},
{"wipe-md", admm_generic, DRBD_acf1_default},
{"apply-al", admm_generic, DRBD_acf1_default},
{"hidden-commands", hidden_cmds,.show_in_usage = 1,},
{"sh-nop", sh_nop, DRBD_acf2_gen_shell .uc_dialog = 1, .test_config = 1},
{"sh-resources", sh_resources, DRBD_acf2_gen_shell},
{"sh-resource", sh_resource, DRBD_acf2_sh_resname},
{"sh-mod-parms", sh_mod_parms, DRBD_acf2_gen_shell},
{"sh-dev", sh_dev, DRBD_acf2_shell},
{"sh-udev", sh_udev, .vol_id_required = 1, DRBD_acf2_hook},
{"sh-minor", sh_minor, DRBD_acf2_shell},
{"sh-ll-dev", sh_ll_dev, DRBD_acf2_shell},
{"sh-md-dev", sh_md_dev, DRBD_acf2_shell},
{"sh-md-idx", sh_md_idx, DRBD_acf2_shell},
{"sh-ip", sh_ip, DRBD_acf2_shell},
{"sh-lr-of", sh_lres, DRBD_acf2_shell},
{"sh-b-pri", sh_b_pri, DRBD_acf2_shell},
{"sh-status", sh_status, DRBD_acf2_gen_shell},
{"proxy-up", adm_proxy_up, DRBD_acf2_proxy},
{"proxy-down", adm_proxy_down, DRBD_acf2_proxy},
{"new-resource", adm_new_resource, DRBD_acf2_sh_resname},
{"sh-new-minor", adm_new_minor, DRBD_acf4_advanced},
{"new-minor", adm_new_minor, DRBD_acf4_advanced}, /* alias for sh-new-minor */
{"before-resync-target", adm_khelper, DRBD_acf3_handler},
{"after-resync-target", adm_khelper, DRBD_acf3_handler},
{"before-resync-source", adm_khelper, DRBD_acf3_handler},
{"pri-on-incon-degr", adm_khelper, DRBD_acf3_handler},
{"pri-lost-after-sb", adm_khelper, DRBD_acf3_handler},
{"fence-peer", adm_khelper, DRBD_acf3_res_handler},
{"unfence-peer", adm_khelper, DRBD_acf3_res_handler},
{"local-io-error", adm_khelper, DRBD_acf3_handler},
{"pri-lost", adm_khelper, DRBD_acf3_handler},
{"initial-split-brain", adm_khelper, DRBD_acf3_handler},
{"split-brain", adm_khelper, DRBD_acf3_handler},
{"out-of-sync", adm_khelper, DRBD_acf3_handler},
{"suspend-io", adm_generic_s, DRBD_acf4_advanced},
{"resume-io", adm_generic_s, DRBD_acf4_advanced},
{"set-gi", admm_generic, DRBD_acf4_advanced_need_vol},
{"new-current-uuid", adm_generic_s, DRBD_acf4_advanced_need_vol
.drbdsetup_ctx = &new_current_uuid_cmd_ctx, },
{"check-resize", adm_chk_resize, DRBD_acf4_advanced},
};
void schedule_deferred_cmd(int (*function) (struct cfg_ctx *),
struct cfg_ctx *ctx,
const char *arg, enum drbd_cfg_stage stage)
{
struct deferred_cmd *d, *t;
d = calloc(1, sizeof(struct deferred_cmd));
if (d == NULL) {
perror("calloc");
exit(E_EXEC_ERROR);
}
d->function = function;
d->ctx.res = ctx->res;
d->ctx.vol = ctx->vol;
d->ctx.arg = arg;
/* first to come is head */
if (!deferred_cmds[stage])
deferred_cmds[stage] = d;
/* link it in at tail */
t = deferred_cmds_tail[stage];
if (t)
t->next = d;
/* advance tail */
deferred_cmds_tail[stage] = d;
}
enum on_error { KEEP_RUNNING, EXIT_ON_FAIL };
int call_cmd_fn(int (*function) (struct cfg_ctx *),
struct cfg_ctx *ctx, enum on_error on_error)
{
int rv;
rv = function(ctx);
if (rv >= 20) {
if (on_error == EXIT_ON_FAIL)
exit(rv);
}
return rv;
}
/* If ctx->vol is NULL, and cmd->iterate_volumes is set,
* iterate over all volumes in ctx->res.
* Else, just pass it on.
* */
int call_cmd(struct adm_cmd *cmd, struct cfg_ctx *ctx,
enum on_error on_error)
{
struct cfg_ctx tmp_ctx = *ctx;
struct d_resource *res = ctx->res;
struct d_volume *vol;
int ret;
if (!res->peer)
set_peer_in_resource(res, cmd->need_peer);
if (!cmd->iterate_volumes || ctx->vol != NULL)
return call_cmd_fn(cmd->function, &tmp_ctx, on_error);
for_each_volume(vol, res->me->volumes) {
tmp_ctx.vol = vol;
ret = call_cmd_fn(cmd->function, &tmp_ctx, on_error);
/* FIXME: Do we want to keep running?
* When?
* How would we determine which return value to return? */
if (ret)
return ret;
}
return 0;
}
static char *drbd_cfg_stage_string[] = {
[CFG_PREREQ] = "create res",
[CFG_RESOURCE] = "adjust res",
[CFG_DISK_PREREQ] = "prepare disk",
[CFG_DISK] = "adjust disk",
[CFG_NET_PREREQ] = "prepare net",
[CFG_NET] = "adjust net",
};
int _run_deferred_cmds(enum drbd_cfg_stage stage)
{
struct d_resource *last_res = NULL;
struct deferred_cmd *d = deferred_cmds[stage];
struct deferred_cmd *t;
int r;
int rv = 0;
if (d && adjust_with_progress) {
printf("\n%15s:", drbd_cfg_stage_string[stage]);
fflush(stdout);
}
while (d) {
if (d->ctx.res->skip_further_deferred_command) {
if (adjust_with_progress) {
if (d->ctx.res != last_res)
printf(" [skipped:%s]", d->ctx.res->name);
} else
err("%s: %s %s: skipped due to earlier error\n",
progname, d->ctx.arg, d->ctx.res->name);
r = 0;
} else {
if (adjust_with_progress) {
if (d->ctx.res != last_res)
printf(" %s", d->ctx.res->name);
}
r = call_cmd_fn(d->function, &d->ctx, KEEP_RUNNING);
if (r) {
/* If something in the "prerequisite" stages failed,
* there is no point in trying to continue.
* However if we just failed to adjust some
* options, or failed to attach, we still want
* to adjust other options, or try to connect.
*/
if (stage == CFG_PREREQ || stage == CFG_DISK_PREREQ)
d->ctx.res->skip_further_deferred_command = 1;
if (adjust_with_progress)
printf(":failed(%s:%u)", d->ctx.arg, r);
}
}
last_res = d->ctx.res;
t = d->next;
free(d);
d = t;
if (r > rv)
rv = r;
}
return rv;
}
int run_deferred_cmds(void)
{
enum drbd_cfg_stage stage;
int r;
int ret = 0;
if (adjust_with_progress)
printf("[");
for (stage = CFG_PREREQ; stage < __CFG_LAST; stage++) {
r = _run_deferred_cmds(stage);
if (r) {
if (!adjust_with_progress)
return 1; /* FIXME r? */
ret = 1;
}
}
if (adjust_with_progress)
printf("\n]\n");
return ret;
}
/*** These functions are used to the print the config ***/
static void dump_options2(char *name, struct d_option *opts,
void(*within)(void*), void *ctx)
{
if (!opts && !(within && ctx))
return;
printI("%s {\n", name);
++indent;
while (opts) {
if (opts->value)
printA(opts->name,
opts->is_escaped ? opts->value : esc(opts->
value));
else
printI(BFMT, opts->name);
opts = opts->next;
}
if (within)
within(ctx);
--indent;
printI("}\n");
}
static void dump_options(char *name, struct d_option *opts)
{
dump_options2(name, opts, NULL, NULL);
}
void dump_proxy_plugins(void *ctx)
{
struct d_option *opt = ctx;
dump_options("plugin", opt);
}
static void dump_global_info()
{
static const char * const yes_no_ask[] = {
[UC_YES] = "yes",
[UC_NO] = "no",
[UC_ASK] = "ask",
};
if (!global_options.minor_count
&& !global_options.disable_ip_verification
&& global_options.dialog_refresh == 1
&& global_options.usage_count == UC_ASK
&& !verbose)
return;
printI("global {\n");
++indent;
if (global_options.disable_ip_verification)
printI("disable-ip-verification;\n");
if (global_options.minor_count)
printI("minor-count %i;\n", global_options.minor_count);
if (global_options.dialog_refresh != 1)
printI("dialog-refresh %i;\n", global_options.dialog_refresh);
if (global_options.usage_count != UC_ASK)
printI("usage-count %s;\n", yes_no_ask[global_options.usage_count]);
if (global_options.cmd_timeout_short != CMD_TIMEOUT_SHORT_DEF)
printI("cmd-timeout-short %u;\n", global_options.cmd_timeout_short);
if (global_options.cmd_timeout_medium != CMD_TIMEOUT_MEDIUM_DEF)
printI("cmd-timeout-medium %u;\n", global_options.cmd_timeout_medium);
if (global_options.cmd_timeout_long != CMD_TIMEOUT_LONG_DEF)
printI("cmd-timeout-long %u;\n", global_options.cmd_timeout_long);
--indent;
printI("}\n\n");
}
static void fake_startup_options(struct d_resource *res);
static void dump_common_info()
{
if (!common)
return;
printI("common {\n");
++indent;
fake_startup_options(common);
dump_options("options", common->res_options);
dump_options("net", common->net_options);
dump_options("disk", common->disk_options);
dump_options("startup", common->startup_options);
dump_options2("proxy", common->proxy_options,
dump_proxy_plugins, common->proxy_plugins);
dump_options("handlers", common->handlers);
--indent;
printf("}\n\n");
}
static void dump_address(char *name, char *addr, char *port, char *af)
{
if (!strcmp(af, "ipv6"))
printI(IPV6FMT, name, af, addr, port);
else
printI(IPV4FMT, name, af, addr, port);
}
static void dump_proxy_info(struct d_proxy_info *pi)
{
printI("proxy on %s {\n", names_to_str(pi->on_hosts));
++indent;
dump_address("inside", pi->inside_addr, pi->inside_port, pi->inside_af);
dump_address("outside", pi->outside_addr, pi->outside_port, pi->outside_af);
dump_options2("options", pi->options,
dump_proxy_plugins, pi->plugins);
--indent;
printI("}\n");
}
static void dump_volume(int has_lower, struct d_volume *vol)
{
if (!vol->implicit) {
printI("volume %d {\n", vol->vnr);
++indent;
}
/* Handle volume of '_remote_host' */
if (!vol->device && !vol->disk && !vol->meta_disk && !vol->meta_index)
goto out;
dump_options("disk", vol->disk_options);
printI("device%*s", -19 + INDENT_WIDTH * indent, "");
if (vol->device)
printf("%s ", esc(vol->device));
printf("minor %d;\n", vol->device_minor);
if (!has_lower)
printA("disk", esc(vol->disk));
if (!has_lower) {
if (!strcmp(vol->meta_index, "flexible"))
printI(MDISK, "meta-disk", esc(vol->meta_disk));
else if (!strcmp(vol->meta_index, "internal"))
printA("meta-disk", "internal");
else
printI(MDISKI, "meta-disk", esc(vol->meta_disk),
vol->meta_index);
}
if (!vol->implicit) {
out:
--indent;
printI("}\n");
}
}
static void dump_host_info(struct d_host_info *hi)
{
struct d_volume *vol;
if (!hi) {
printI(" # No host section data available.\n");
return;
}
if (hi->lower) {
printI("stacked-on-top-of %s {\n", esc(hi->lower->name));
++indent;
printI("# on %s \n", names_to_str(hi->on_hosts));
} else if (hi->by_address) {
if (!strcmp(hi->address_family, "ipv6"))
printI("floating ipv6 [%s]:%s {\n", hi->address, hi->port);
else
printI("floating %s %s:%s {\n", hi->address_family, hi->address, hi->port);
++indent;
} else {
printI("on %s {\n", names_to_str(hi->on_hosts));
++indent;
}
dump_options("options", hi->res_options);
for_each_volume(vol, hi->volumes)
dump_volume(!!hi->lower, vol);
if (!hi->by_address)
dump_address("address", hi->address, hi->port, hi->address_family);
if (hi->alt_address)
dump_address("alternate-link-address", hi->alt_address, hi->alt_port, hi->alt_address_family);
if (hi->proxy)
dump_proxy_info(hi->proxy);
--indent;
printI("}\n");
}
static void dump_options_xml2(char *name, struct d_option *opts,
void(*within)(void*), void *ctx)
{
if (!opts && !(within && ctx))
return;
printI("<section name=\"%s\">\n", name);
++indent;
while (opts) {
if (opts->value)
printI("<option name=\"%s\" value=\"%s\"/>\n",
opts->name,
opts->is_escaped ? opts->value : esc_xml(opts->
value));
else
printI("<option name=\"%s\"/>\n", opts->name);
opts = opts->next;
}
if (within)
within(ctx);
--indent;
printI("</section>\n");
}
static void dump_options_xml(char *name, struct d_option *opts)
{
dump_options_xml2(name, opts, NULL, NULL);
}
void dump_proxy_plugins_xml(void *ctx)
{
struct d_option *opt = ctx;
dump_options_xml("plugin", opt);
}
static void dump_global_info_xml()
{
if (!global_options.minor_count
&& !global_options.disable_ip_verification
&& global_options.dialog_refresh == 1)
return;
printI("<global>\n");
++indent;
if (global_options.disable_ip_verification)
printI("<disable-ip-verification/>\n");
if (global_options.minor_count)
printI("<minor-count count=\"%i\"/>\n",
global_options.minor_count);
if (global_options.dialog_refresh != 1)
printI("<dialog-refresh refresh=\"%i\"/>\n",
global_options.dialog_refresh);
--indent;
printI("</global>\n");
}
static void dump_common_info_xml()
{
if (!common)
return;
printI("<common>\n");
++indent;
fake_startup_options(common);
dump_options_xml("options", common->res_options);
dump_options_xml("net", common->net_options);
dump_options_xml("disk", common->disk_options);
dump_options_xml("startup", common->startup_options);
dump_options_xml2("proxy", common->proxy_options,
dump_proxy_plugins_xml, common->proxy_plugins);
dump_options_xml("handlers", common->handlers);
--indent;
printI("</common>\n");
}
static void dump_proxy_info_xml(struct d_proxy_info *pi)
{
printI("<proxy hostname=\"%s\">\n", names_to_str(pi->on_hosts));
++indent;
printI("<inside family=\"%s\" port=\"%s\">%s</inside>\n", pi->inside_af,
pi->inside_port, pi->inside_addr);
printI("<outside family=\"%s\" port=\"%s\">%s</outside>\n",
pi->outside_af, pi->outside_port, pi->outside_addr);
dump_options_xml2("options", pi->options,
dump_proxy_plugins_xml, pi->plugins);
--indent;
printI("</proxy>\n");
}
static void dump_volume_xml(struct d_volume *vol)
{
printI("<volume vnr=\"%d\">\n", vol->vnr);
++indent;
dump_options_xml("disk", vol->disk_options);
printI("<device minor=\"%d\">%s</device>\n", vol->device_minor,
esc_xml(vol->device));
printI("<disk>%s</disk>\n", esc_xml(vol->disk));
if (!strcmp(vol->meta_index, "flexible"))
printI("<meta-disk>%s</meta-disk>\n",
esc_xml(vol->meta_disk));
else if (!strcmp(vol->meta_index, "internal"))
printI("<meta-disk>internal</meta-disk>\n");
else {
printI("<meta-disk index=\"%s\">%s</meta-disk>\n",
vol->meta_index, esc_xml(vol->meta_disk));
}
--indent;
printI("</volume>\n");
}
static void dump_host_info_xml(struct d_host_info *hi)
{
struct d_volume *vol;
if (!hi) {
printI("<!-- No host section data available. -->\n");
return;
}
if (hi->by_address)
printI("<host floating=\"1\">\n");
else
printI("<host name=\"%s\">\n", names_to_str(hi->on_hosts));
++indent;
dump_options_xml("options", hi->res_options);
for_each_volume(vol, hi->volumes)
dump_volume_xml(vol);
printI("<address family=\"%s\" port=\"%s\">%s</address>\n",
hi->address_family, hi->port, hi->address);
if (hi->proxy)
dump_proxy_info_xml(hi->proxy);
--indent;
printI("</host>\n");
}
static void fake_startup_options(struct d_resource *res)
{
struct d_option *opt;
char *val;
if (res->stacked_timeouts) {
opt = new_opt(strdup("stacked-timeouts"), NULL);
res->startup_options = APPEND(res->startup_options, opt);
}
if (res->become_primary_on) {
val = strdup(names_to_str(res->become_primary_on));
opt = new_opt(strdup("become-primary-on"), val);
opt->is_escaped = 1;
res->startup_options = APPEND(res->startup_options, opt);
}
}
static int adm_dump(struct cfg_ctx *ctx)
{
struct d_host_info *host;
struct d_resource *res = ctx->res;
if (!res) {
printI("# no resources configured\n");
return 0;
}
printI("# resource %s on %s: %s, %s\n",
esc(res->name), nodeinfo.nodename,
res->ignore ? "ignored" : "not ignored",
res->stacked ? "stacked" : "not stacked");
printI("# defined at %s:%u\n", res->config_file, res->start_line);
printI("resource %s {\n", esc(res->name));
++indent;
for (host = res->all_hosts; host; host = host->next)
dump_host_info(host);
fake_startup_options(res);
dump_options("options", res->res_options);
dump_options("net", res->net_options);
dump_options("disk", res->disk_options);
dump_options("startup", res->startup_options);
dump_options2("proxy", res->proxy_options,
dump_proxy_plugins, res->proxy_plugins);
dump_options("handlers", res->handlers);
--indent;
printf("}\n\n");
return 0;
}
static int adm_dump_xml(struct cfg_ctx *ctx)
{
struct d_host_info *host;
struct d_resource *res = ctx->res;
if (!res) {
printI("<!-- No resources configured -->\n");
return 0;
}
printI("<resource name=\"%s\" conf-file-line=\"%s:%u\">\n",
esc_xml(res->name),
esc_xml(res->config_file), res->start_line);
++indent;
// else if (common && common->protocol) printA("# common protocol", common->protocol);
for (host = res->all_hosts; host; host = host->next)
dump_host_info_xml(host);
fake_startup_options(res);
dump_options_xml("options", res->res_options);
dump_options_xml("net", res->net_options);
dump_options_xml("disk", res->disk_options);
dump_options_xml("startup", res->startup_options);
dump_options_xml2("proxy", res->proxy_options,
dump_proxy_plugins_xml, res->proxy_plugins);
dump_options_xml("handlers", res->handlers);
--indent;
printI("</resource>\n");
return 0;
}
static int sh_nop(struct cfg_ctx *ctx)
{
return 0;
}
static int sh_resources(struct cfg_ctx *ctx)
{
struct d_resource *res, *t;
int first = 1;
for_each_resource(res, t, config) {
if (res->ignore)
continue;
if (is_drbd_top != res->stacked)
continue;
printf(first ? "%s" : " %s", esc(res->name));
first = 0;
}
if (!first)
printf("\n");
return 0;
}
static int sh_resource(struct cfg_ctx *ctx)
{
printf("%s\n", ctx->res->name);
return 0;
}
static int sh_dev(struct cfg_ctx *ctx)
{
printf("%s\n", ctx->vol->device);
return 0;
}
static int sh_udev(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
struct d_volume *vol = ctx->vol;
/* No shell escape necessary. Udev does not handle it anyways... */
if (!vol) {
err("volume not specified\n");
return 1;
}
if (vol->implicit && !global_options.udev_always_symlink_vnr)
printf("RESOURCE=%s\n", res->name);
else
printf("RESOURCE=%s/%u\n", res->name, vol->vnr);
if (!strncmp(vol->device, "/dev/drbd", 9))
printf("DEVICE=%s\n", vol->device + 5);
else
printf("DEVICE=drbd%u\n", vol->device_minor);
if (!strncmp(vol->disk, "/dev/", 5))
printf("DISK=%s\n", vol->disk + 5);
else
printf("DISK=%s\n", vol->disk);
return 0;
}
static int sh_minor(struct cfg_ctx *ctx)
{
printf("%d\n", ctx->vol->device_minor);
return 0;
}
static int sh_ip(struct cfg_ctx *ctx)
{
printf("%s\n", ctx->res->me->address);
return 0;
}
static int sh_lres(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
if (!is_drbd_top) {
err("sh-lower-resource only available in stacked mode\n");
exit(E_USAGE);
}
if (!res->stacked) {
err("'%s' is not stacked on this host (%s)\n", res->name, nodeinfo.nodename);
exit(E_USAGE);
}
printf("%s\n", res->me->lower->name);
return 0;
}
static int sh_ll_dev(struct cfg_ctx *ctx)
{
printf("%s\n", ctx->vol->disk);
return 0;
}
static int sh_md_dev(struct cfg_ctx *ctx)
{
struct d_volume *vol = ctx->vol;
char *r;
if (strcmp("internal", vol->meta_disk) == 0)
r = vol->disk;
else
r = vol->meta_disk;
printf("%s\n", r);
return 0;
}
static int sh_md_idx(struct cfg_ctx *ctx)
{
printf("%s\n", ctx->vol->meta_index);
return 0;
}
static int sh_b_pri(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
int i, rv;
if (name_in_names(nodeinfo.nodename, res->become_primary_on) ||
name_in_names("both", res->become_primary_on)) {
/* upon connect resync starts, and both sides become primary at the same time.
One's try might be declined since an other state transition happens. Retry. */
for (i = 0; i < 5; i++) {
const char *old_arg = ctx->arg;
ctx->arg = "primary";
rv = adm_generic_s(ctx);
ctx->arg = old_arg;
if (rv == 0)
return rv;
sleep(1);
}
return rv;
}
return 0;
}
/* FIXME this module parameter will go */
static int sh_mod_parms(struct cfg_ctx *ctx)
{
int mc = global_options.minor_count;
if (mc == 0) {
mc = number_of_minors + 3;
if (mc > DRBD_MINOR_COUNT_MAX)
mc = DRBD_MINOR_COUNT_MAX;
if (mc < DRBD_MINOR_COUNT_DEF)
mc = DRBD_MINOR_COUNT_DEF;
}
printf("minor_count=%d\n", mc);
return 0;
}
static void free_volume(struct d_volume *vol)
{
if (!vol)
return;
free(vol->device);
free(vol->disk);
free(vol->meta_disk);
free(vol->meta_index);
free(vol);
}
static void free_host_info(struct d_host_info *hi)
{
struct d_volume *vol;
if (!hi)
return;
free_names(hi->on_hosts);
while ((vol = hi->volumes)) {
hi->volumes = vol->next;
free_volume(vol);
}
free(hi->address);
free(hi->address_family);
free(hi->port);
free(hi);
}
static void free_options(struct d_option *opts)
{
struct d_option *f;
while (opts) {
free(opts->name);
free(opts->value);
f = opts;
opts = opts->next;
free(f);
}
}
static void free_config(struct d_resource *res)
{
struct d_resource *f;
struct d_host_info *host;
while ((f = res)) {
res = f->next;
free(f->name);
free_volume(f->volumes);
while ((host = f->all_hosts)) {
f->all_hosts = host->next;
free_host_info(host);
}
free_options(f->net_options);
free_options(f->disk_options);
free_options(f->startup_options);
free_options(f->proxy_options);
free_options(f->handlers);
free(f);
}
if (common) {
free_options(common->net_options);
free_options(common->disk_options);
free_options(common->startup_options);
free_options(common->proxy_options);
free_options(common->handlers);
free(common);
}
if (ifreq_list)
free(ifreq_list);
}
static void expand_opts(struct d_option *co, struct d_option **opts)
{
struct d_option *no;
while (co) {
if (!find_opt(*opts, co->name)) {
// prepend new item to opts
no = new_opt(strdup(co->name),
co->value ? strdup(co->value) : NULL);
no->next = *opts;
*opts = no;
}
co = co->next;
}
}
static void expand_common(void)
{
struct d_resource *res, *tmp;
struct d_volume *vol, *host_vol;
struct d_host_info *h;
/* make sure vol->device is non-NULL */
for_each_resource(res, tmp, config) {
for (h = res->all_hosts; h; h = h->next) {
for_each_volume(vol, h->volumes) {
if (!vol->device)
m_asprintf(&vol->device, "/dev/drbd%u",
vol->device_minor);
}
}
}
for_each_resource(res, tmp, config) {
if (!common)
break;
expand_opts(common->net_options, &res->net_options);
expand_opts(common->disk_options, &res->disk_options);
expand_opts(common->startup_options, &res->startup_options);
expand_opts(common->proxy_options, &res->proxy_options);
expand_opts(common->handlers, &res->handlers);
expand_opts(common->res_options, &res->res_options);
if (common->stacked_timeouts)
res->stacked_timeouts = 1;
if (!res->become_primary_on)
res->become_primary_on = common->become_primary_on;
if (common->proxy_plugins && !res->proxy_plugins)
expand_opts(common->proxy_plugins, &res->proxy_plugins);
}
/* now that common disk options (if any) have been propagated to the
* resource level, further propagate them to the volume level. */
for_each_resource(res, tmp, config) {
for (h = res->all_hosts; h; h = h->next) {
for_each_volume(vol, h->volumes) {
expand_opts(res->disk_options, &vol->disk_options);
}
if (h->proxy) {
expand_opts(res->proxy_options, &h->proxy->options);
expand_opts(res->proxy_plugins, &h->proxy->plugins);
}
}
}
/* now from all volume/disk-options on resource level to host level */
for_each_resource(res, tmp, config) {
for_each_volume(vol, res->volumes) {
for (h = res->all_hosts; h; h = h->next) {
host_vol = volume_by_vnr(h->volumes, vol->vnr);
expand_opts(vol->disk_options, &host_vol->disk_options);
}
}
}
}
static void find_drbdcmd(char **cmd, char **pathes)
{
char **path;
path = pathes;
while (*path) {
if (access(*path, X_OK) == 0) {
*cmd = *path;
return;
}
path++;
}
err("Can not find command (drbdsetup/drbdmeta)\n");
exit(E_EXEC_ERROR);
}
#define NA(ARGC) \
({ if((ARGC) >= MAX_ARGS) { err("MAX_ARGS too small\n"); \
exit(E_THINKO); \
} \
(ARGC)++; \
})
static void add_setup_options(char **argv, int *argcp)
{
int argc = *argcp;
int i;
if (!setup_options)
return;
for (i = 0; setup_options[i].option; i++)
argv[NA(argc)] = setup_options[i].option;
*argcp = argc;
}
#define make_options(OPT) \
while(OPT) { \
if(OPT->value) { \
ssprintf(argv[NA(argc)],"--%s=%s",OPT->name,OPT->value); \
} else { \
ssprintf(argv[NA(argc)],"--%s",OPT->name); \
} \
OPT=OPT->next; \
}
/* FIXME: Don't leak the memory allocated by asprintf. */
#define make_address(ADDR, PORT, AF) \
if (!strcmp(AF, "ipv6")) { \
m_asprintf(&argv[NA(argc)], "%s:[%s]:%s", AF, ADDR, PORT); \
} else { \
m_asprintf(&argv[NA(argc)], "%s:%s:%s", AF, ADDR, PORT); \
}
static int adm_attach_or_disk_options(struct cfg_ctx *ctx, bool do_attach, bool reset)
{
struct d_volume *vol = ctx->vol;
char *argv[MAX_ARGS];
struct d_option *opt;
int argc = 0;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = do_attach ? "attach" : "disk-options";
ssprintf(argv[NA(argc)], "%d", vol->device_minor);
if (do_attach) {
argv[NA(argc)] = vol->disk;
if (!strcmp(vol->meta_disk, "internal")) {
argv[NA(argc)] = vol->disk;
} else {
argv[NA(argc)] = vol->meta_disk;
}
argv[NA(argc)] = vol->meta_index;
}
if (reset)
argv[NA(argc)] = "--set-defaults";
if (reset || do_attach) {
opt = ctx->vol->disk_options;
if (!do_attach) {
while (opt && opt->adj_skip)
opt = opt->next;
}
make_options(opt);
}
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
return m_system_ex(argv, SLEEPS_LONG, ctx->res->name);
}
int adm_attach(struct cfg_ctx *ctx)
{
int rv;
ctx->arg = "apply-al";
rv = admm_generic(ctx);
if (rv)
return rv;
ctx->arg = "attach";
return adm_attach_or_disk_options(ctx, true, false);
}
int adm_disk_options(struct cfg_ctx *ctx)
{
return adm_attach_or_disk_options(ctx, false, false);
}
int adm_set_default_disk_options(struct cfg_ctx *ctx)
{
return adm_attach_or_disk_options(ctx, false, true);
}
int adm_status(struct cfg_ctx *ctx)
{
return adm_generic_s(ctx);
}
struct d_option *find_opt(struct d_option *base, char *name)
{
while (base) {
if (!strcmp(base->name, name)) {
return base;
}
base = base->next;
}
return 0;
}
int adm_new_minor(struct cfg_ctx *ctx)
{
char *argv[MAX_ARGS];
int argc = 0, ex;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = "new-minor";
ssprintf(argv[NA(argc)], "%s", ctx->res->name);
ssprintf(argv[NA(argc)], "%u", ctx->vol->device_minor);
ssprintf(argv[NA(argc)], "%u", ctx->vol->vnr);
argv[NA(argc)] = NULL;
ex = m_system_ex(argv, SLEEPS_SHORT, ctx->res->name);
if (!ex && do_register)
register_minor(ctx->vol->device_minor, config_save);
return ex;
}
static int adm_new_resource_or_res_options(struct cfg_ctx *ctx, bool do_new_resource, bool reset)
{
char *argv[MAX_ARGS];
int argc = 0, ex;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = do_new_resource ? "new-resource" : "resource-options";
ssprintf(argv[NA(argc)], "%s", ctx->res->name);
if (reset)
argv[NA(argc)] = "--set-defaults";
if (reset || do_new_resource)
make_options(ctx->res->res_options);
add_setup_options(argv, &argc);
argv[NA(argc)] = NULL;
ex = m_system_ex(argv, SLEEPS_SHORT, ctx->res->name);
if (!ex && do_new_resource && do_register)
register_resource(ctx->res->name, config_save);
return ex;
}
int adm_new_resource(struct cfg_ctx *ctx)
{
return adm_new_resource_or_res_options(ctx, true, false);
}
int adm_res_options(struct cfg_ctx *ctx)
{
return adm_new_resource_or_res_options(ctx, false, false);
}
int adm_set_default_res_options(struct cfg_ctx *ctx)
{
return adm_new_resource_or_res_options(ctx, false, true);
}
int adm_resize(struct cfg_ctx *ctx)
{
char *argv[MAX_ARGS];
struct d_option *opt;
int argc = 0;
int silent;
int ex;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = "resize";
ssprintf(argv[NA(argc)], "%d", ctx->vol->device_minor);
opt = find_opt(ctx->vol->disk_options, "size");
if (!opt)
opt = find_opt(ctx->res->disk_options, "size");
if (opt)
ssprintf(argv[NA(argc)], "--%s=%s", opt->name, opt->value);
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
/* if this is not "resize", but "check-resize", be silent! */
silent = !strcmp(ctx->arg, "check-resize") ? SUPRESS_STDERR : 0;
ex = m_system_ex(argv, SLEEPS_SHORT | silent, ctx->res->name);
if (ex)
return ex;
/* Record last-known bdev info.
* Unfortunately drbdsetup did not have enough information
* when doing the "resize", and in theory, _our_ information
* about the backing device may even be wrong.
* Call drbdsetup again, tell it to ask the kernel for
* current config, and update the last known bdev info
* according to that. */
/* argv[0] = drbdsetup; */
argv[1] = "check-resize";
/* argv[2] = minor; */
argv[3] = NULL;
/* ignore exit code */
m_system_ex(argv, SLEEPS_SHORT | silent, ctx->res->name);
return 0;
}
int _admm_generic(struct cfg_ctx *ctx, int flags)
{
struct d_volume *vol = ctx->vol;
char *argv[MAX_ARGS];
int argc = 0;
argv[NA(argc)] = drbdmeta;
ssprintf(argv[NA(argc)], "%d", vol->device_minor);
argv[NA(argc)] = "v08";
if (!strcmp(vol->meta_disk, "internal")) {
argv[NA(argc)] = vol->disk;
} else {
argv[NA(argc)] = vol->meta_disk;
}
if (!strcmp(vol->meta_index, "flexible")) {
if (!strcmp(vol->meta_disk, "internal")) {
argv[NA(argc)] = "flex-internal";
} else {
argv[NA(argc)] = "flex-external";
}
} else {
argv[NA(argc)] = vol->meta_index;
}
argv[NA(argc)] = (char *)ctx->arg;
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
return m_system_ex(argv, flags, ctx->res->name);
}
static int admm_generic(struct cfg_ctx *ctx)
{
return _admm_generic(ctx, SLEEPS_VERY_LONG);
}
static void _adm_generic(struct cfg_ctx *ctx, int flags, pid_t *pid, int *fd, int *ex)
{
char *argv[MAX_ARGS];
int argc = 0;
if (!ctx->res) {
/* ASSERT */
err("sorry, need at least a resource name to call drbdsetup\n");
abort();
}
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = (char *)ctx->arg;
if (ctx->vol)
ssprintf(argv[NA(argc)], "%d", ctx->vol->device_minor);
else
ssprintf(argv[NA(argc)], "%s", ctx->res->name);
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
setenv("DRBD_RESOURCE", ctx->res->name, 1);
m__system(argv, flags, ctx->res->name, pid, fd, ex);
}
static int adm_generic(struct cfg_ctx *ctx, int flags)
{
int ex;
_adm_generic(ctx, flags, NULL, NULL, &ex);
return ex;
}
int adm_generic_s(struct cfg_ctx *ctx)
{
return adm_generic(ctx, SLEEPS_SHORT);
}
int sh_status(struct cfg_ctx *ctx)
{
struct d_resource *r, *t;
struct d_volume *vol, *lower_vol;
int rv = 0;
if (!dry_run) {
printf("_drbd_version=%s\n_drbd_api=%u\n",
shell_escape(PACKAGE_VERSION), API_VERSION);
printf("_config_file=%s\n\n\n", shell_escape(config_save));
}
for_each_resource(r, t, config) {
if (r->ignore)
continue;
ctx->res = r;
printf("_conf_res_name=%s\n", shell_escape(r->name));
printf("_conf_file_line=%s:%u\n\n", shell_escape(r->config_file), r->start_line);
if (r->stacked && r->me->lower) {
printf("_stacked_on=%s\n", shell_escape(r->me->lower->name));
lower_vol = r->me->lower->me->volumes;
} else {
/* reset stuff */
printf("_stacked_on=\n");
printf("_stacked_on_device=\n");
printf("_stacked_on_minor=\n");
lower_vol = NULL;
}
/* TODO: remove this loop, have drbdsetup use dump
* and optionally filter on resource name.
* "stacked" information is not directly known to drbdsetup, though.
*/
for_each_volume(vol, r->me->volumes) {
/* do not continue in this loop,
* or lower_vol will get out of sync */
if (lower_vol) {
printf("_stacked_on_device=%s\n", shell_escape(lower_vol->device));
printf("_stacked_on_minor=%d\n", lower_vol->device_minor);
} else if (r->stacked && r->me->lower) {
/* ASSERT */
err("in %s: stacked volume[%u] without lower volume\n",
r->name, vol->vnr);
abort();
}
printf("_conf_volume=%d\n", vol->vnr);
ctx->vol = vol;
rv = adm_generic(ctx, SLEEPS_SHORT);
if (rv)
return rv;
if (lower_vol)
lower_vol = lower_vol->next;
/* vol is advanced by for_each_volume */
}
}
return 0;
}
int adm_generic_l(struct cfg_ctx *ctx)
{
return adm_generic(ctx, SLEEPS_LONG);
}
static int adm_outdate(struct cfg_ctx *ctx)
{
int rv;
rv = adm_generic(ctx, SLEEPS_SHORT | SUPRESS_STDERR);
/* special cases for outdate:
* 17: drbdsetup outdate, but is primary and thus cannot be outdated.
* 5: drbdsetup outdate, and is inconsistent or worse anyways. */
if (rv == 17)
return rv;
if (rv == 5) {
/* That might mean it is diskless. */
rv = admm_generic(ctx);
if (rv)
rv = 5;
return rv;
}
if (rv || dry_run) {
rv = admm_generic(ctx);
}
return rv;
}
/* shell equivalent:
* ( drbdsetup resize && drbdsetup check-resize ) || drbdmeta check-resize */
static int adm_chk_resize(struct cfg_ctx *ctx)
{
/* drbdsetup resize && drbdsetup check-resize */
int ex = adm_resize(ctx);
if (ex == 0)
return 0;
/* try drbdmeta check-resize */
return admm_generic(ctx);
}
static int adm_generic_b(struct cfg_ctx *ctx)
{
char buffer[4096];
int fd, status, rv = 0, rr, s = 0;
pid_t pid;
_adm_generic(ctx, SLEEPS_SHORT | RETURN_STDERR_FD, &pid, &fd, NULL);
if (!dry_run) {
if (fd < 0) {
err("Strange: got negative fd.\n");
exit(E_THINKO);
}
while (1) {
rr = read(fd, buffer + s, 4096 - s);
if (rr <= 0)
break;
s += rr;
}
close(fd);
rr = waitpid(pid, &status, 0);
alarm(0);
if (WIFEXITED(status))
rv = WEXITSTATUS(status);
if (alarm_raised) {
rv = 0x100;
}
}
/* see drbdsetup.c, print_config_error():
* 11: some unspecific state change error
* 17: SS_NO_UP_TO_DATE_DISK
* In both cases, we don't need to retry with drbdmeta,
* it would fail anyways with "Device is configured!" */
if (rv == 11 || rv == 17) {
/* Some state transition error, report it ... */
rr = write(fileno(stderr), buffer, s);
return rv;
}
if (rv || dry_run) {
/* On other errors
rv = 10 .. no minor allocated
rv = 20 .. module not loaded
rv = 16 .. we are diskless here
retry with drbdmeta.
*/
rv = admm_generic(ctx);
}
return rv;
}
static int adm_khelper(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
struct d_volume *vol = ctx->vol;
int rv = 0;
char *sh_cmd;
char minor_string[8];
char volume_string[8];
char *argv[] = { "/bin/sh", "-c", NULL, NULL };
if (!res->peer) {
/* Since 8.3.2 we get DRBD_PEER_AF and DRBD_PEER_ADDRESS from the kernel.
If we do not know the peer by now, use these to find the peer. */
struct d_host_info *host;
char *peer_address = getenv("DRBD_PEER_ADDRESS");
char *peer_af = getenv("DRBD_PEER_AF");
if (peer_address && peer_af) {
for (host = res->all_hosts; host; host = host->next) {
if (!strcmp(host->address_family, peer_af) &&
!strcmp(host->address, peer_address)) {
res->peer = host;
break;
}
}
}
}
if (res->peer) {
setenv("DRBD_PEER_AF", res->peer->address_family, 1); /* since 8.3.0 */
setenv("DRBD_PEER_ADDRESS", res->peer->address, 1); /* since 8.3.0 */
setenv("DRBD_PEER", res->peer->on_hosts->name, 1); /* deprecated */
setenv("DRBD_PEERS", names_to_str(res->peer->on_hosts), 1);
/* since 8.3.0, but not usable when using a config with "floating" statements. */
}
if (vol) {
snprintf(minor_string, sizeof(minor_string), "%u", vol->device_minor);
snprintf(volume_string, sizeof(volume_string), "%u", vol->vnr);
setenv("DRBD_MINOR", minor_string, 1);
setenv("DRBD_VOLUME", volume_string, 1);
setenv("DRBD_LL_DISK", vol->disk, 1);
} else {
char *minor_list;
char *separator = "";
char *pos;
int volumes = 0;
int bufsize;
int n;
for_each_volume(vol, res->me->volumes)
volumes++;
/* max minor number is 2**20 - 1, which is 7 decimal digits.
* plus separator respective trailing zero. */
bufsize = volumes * 8 + 1;
minor_list = alloca(bufsize);
pos = minor_list;
for_each_volume(vol, res->me->volumes) {
n = snprintf(pos, bufsize, "%s%d", separator, vol->device_minor);
if (n >= bufsize) {
/* "can not happen" */
err("buffer too small when generating the minor list\n");
abort();
break;
}
bufsize -= n;
pos += n;
separator = " ";
}
setenv("DRBD_MINOR", minor_list, 1);
}
setenv("DRBD_RESOURCE", res->name, 1);
setenv("DRBD_CONF", config_save, 1);
if ((sh_cmd = get_opt_val(res->handlers, ctx->arg, NULL))) {
argv[2] = sh_cmd;
rv = m_system_ex(argv, SLEEPS_VERY_LONG, res->name);
}
return rv;
}
// need to convert discard-node-nodename to discard-local or discard-remote.
void convert_discard_opt(struct d_resource *res)
{
struct d_option *opt;
if (res == NULL)
return;
if ((opt = find_opt(res->net_options, "after-sb-0pri"))) {
if (!strncmp(opt->value, "discard-node-", 13)) {
if (!strcmp(nodeinfo.nodename, opt->value + 13)) {
free(opt->value);
opt->value = strdup("discard-local");
} else {
free(opt->value);
opt->value = strdup("discard-remote");
}
}
}
}
static int add_connection_endpoints(char **argv, int *argcp, struct d_resource *res)
{
int argc = *argcp;
make_address(res->me->address, res->me->port, res->me->address_family);
if (res->me->proxy) {
make_address(res->me->proxy->inside_addr,
res->me->proxy->inside_port,
res->me->proxy->inside_af);
} else if (res->peer) {
make_address(res->peer->address, res->peer->port,
res->peer->address_family);
} else if (dry_run) {
argv[NA(argc)] = "N/A";
} else {
err("resource %s: cannot configure network without knowing my peer.\n", res->name);
return 20;
}
*argcp = argc;
return 0;
}
static int adm_connect_or_net_options(struct cfg_ctx *ctx, bool do_connect, bool reset)
{
struct d_resource *res = ctx->res;
char *argv[MAX_ARGS];
struct d_option *opt;
int argc = 0;
int ret;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = do_connect ? "connect" : "net-options";
if (do_connect)
ssprintf(argv[NA(argc)], "%s", res->name);
ret = add_connection_endpoints(argv, &argc, res);
if (ret)
return ret;
if (do_connect && res->me->alt_address) {
ssprintf(argv[NA(argc)], "--alternate-address");
make_address(res->me->alt_address, res->me->alt_port, res->me->alt_address_family);
ssprintf(argv[NA(argc)], "--alternate-peer-address");
make_address(res->peer->alt_address, res->peer->alt_port, res->peer->alt_address_family);
}
if (reset)
argv[NA(argc)] = "--set-defaults";
if (reset || do_connect) {
opt = res->net_options;
make_options(opt);
}
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
return m_system_ex(argv, SLEEPS_SHORT, res->name);
}
int adm_connect(struct cfg_ctx *ctx)
{
return adm_connect_or_net_options(ctx, true, false);
}
int adm_net_options(struct cfg_ctx *ctx)
{
return adm_connect_or_net_options(ctx, false, false);
}
int adm_set_default_net_options(struct cfg_ctx *ctx)
{
return adm_connect_or_net_options(ctx, false, true);
}
int adm_disconnect(struct cfg_ctx *ctx)
{
char *argv[MAX_ARGS];
int argc = 0;
if (!ctx->res) {
/* ASSERT */
err("sorry, need at least a resource name to call drbdsetup\n");
abort();
}
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = (char *)ctx->arg;
add_connection_endpoints(argv, &argc, ctx->res);
add_setup_options(argv, &argc);
argv[NA(argc)] = 0;
setenv("DRBD_RESOURCE", ctx->res->name, 1);
return m_system_ex(argv, SLEEPS_SHORT, ctx->res->name);
}
struct d_option *del_opt(struct d_option *base, struct d_option *item)
{
struct d_option *i;
if (base == item) {
base = item->next;
free(item->name);
free(item->value);
free(item);
return base;
}
for (i = base; i; i = i->next) {
if (i->next == item) {
i->next = item->next;
free(item->name);
free(item->value);
free(item);
return base;
}
}
return base;
}
// Need to convert after from resourcename to minor_number.
void _convert_after_option(struct d_resource *res, struct d_volume *vol)
{
struct d_option *opt, *next;
struct cfg_ctx depends_on_ctx = { };
int volumes;
if (res == NULL)
return;
opt = vol->disk_options;
while ((opt = find_opt(opt, "resync-after"))) {
next = opt->next;
ctx_by_name(&depends_on_ctx, opt->value);
volumes = ctx_set_implicit_volume(&depends_on_ctx);
if (volumes > 1) {
err("%s:%d: in resource %s:\n\t"
"resync-after contains '%s', which is ambiguous, since it contains %d volumes\n",
res->config_file, res->start_line, res->name,
opt->value, volumes);
config_valid = 0;
return;
}
if (!depends_on_ctx.res || depends_on_ctx.res->ignore) {
vol->disk_options = del_opt(vol->disk_options, opt);
} else {
free(opt->value);
m_asprintf(&opt->value, "%d", depends_on_ctx.vol->device_minor);
}
opt = next;
}
}
// Need to convert after from resourcename/volume to minor_number.
void convert_after_option(struct d_resource *res)
{
struct d_volume *vol;
struct d_host_info *h;
for (h = res->all_hosts; h; h = h->next)
for_each_volume(vol, h->volumes)
_convert_after_option(res, vol);
}
int _proxy_connect_name_len(struct d_resource *res)
{
return strlen(res->name) +
strlen(names_to_str_c(res->peer->proxy->on_hosts, '_')) +
strlen(names_to_str_c(res->me->proxy->on_hosts, '_')) +
3 /* for the two dashes and the trailing 0 character */;
}
char *_proxy_connection_name(char *conn_name, struct d_resource *res)
{
sprintf(conn_name, "%s-%s-%s",
res->name,
names_to_str_c(res->peer->proxy->on_hosts, '_'),
names_to_str_c(res->me->proxy->on_hosts, '_'));
return conn_name;
}
int do_proxy_conn_up(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
char *argv[4] = { drbd_proxy_ctl, "-c", NULL, NULL };
char *conn_name;
int rv;
conn_name = proxy_connection_name(res);
ssprintf(argv[2],
"add connection %s %s:%s %s:%s %s:%s %s:%s",
conn_name,
res->me->proxy->inside_addr,
res->me->proxy->inside_port,
res->peer->proxy->outside_addr,
res->peer->proxy->outside_port,
res->me->proxy->outside_addr,
res->me->proxy->outside_port, res->me->address,
res->me->port);
rv = m_system_ex(argv, SLEEPS_SHORT, res->name);
return rv;
}
int do_proxy_conn_plugins(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
char *argv[MAX_ARGS];
char *conn_name;
int argc = 0;
struct d_option *opt;
int counter;
conn_name = proxy_connection_name(res);
argc = 0;
argv[NA(argc)] = drbd_proxy_ctl;
opt = res->me->proxy->options;
while (opt) {
argv[NA(argc)] = "-c";
ssprintf(argv[NA(argc)], "set %s %s %s",
opt->name, conn_name, opt->value);
opt = opt->next;
}
counter = 0;
opt = res->me->proxy->plugins;
/* Don't send the "set plugin ... END" line if no plugins are defined
* - that's incompatible with the drbd proxy version 1. */
if (opt) {
while (1) {
argv[NA(argc)] = "-c";
ssprintf(argv[NA(argc)], "set plugin %s %d %s",
conn_name, counter, opt ? opt->name : "END");
if (!opt) break;
opt = opt->next;
counter ++;
}
}
argv[NA(argc)] = 0;
if (argc > 2)
return m_system_ex(argv, SLEEPS_SHORT, res->name);
return 0;
}
int do_proxy_conn_down(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
char *conn_name;
char *argv[4] = { drbd_proxy_ctl, "-c", NULL, NULL};
int rv;
conn_name = proxy_connection_name(res);
ssprintf(argv[2], "del connection %s", conn_name);
rv = m_system_ex(argv, SLEEPS_SHORT, res->name);
return rv;
}
static int check_proxy(struct cfg_ctx *ctx, int do_up)
{
struct d_resource *res = ctx->res;
int rv;
if (!res->me->proxy) {
if (all_resources)
return 0;
err("There is no proxy config for host %s in resource %s.\n",
nodeinfo.nodename, res->name);
exit(E_CONFIG_INVALID);
}
if (!name_in_names(nodeinfo.nodename, res->me->proxy->on_hosts)) {
if (all_resources)
return 0;
err("The proxy config in resource %s is not for %s.\n",
res->name, nodeinfo.nodename);
exit(E_CONFIG_INVALID);
}
if (!res->peer) {
err("Cannot determine the peer in resource %s.\n", res->name);
exit(E_CONFIG_INVALID);
}
if (!res->peer->proxy) {
err("There is no proxy config for the peer in resource %s.\n",
res->name);
if (all_resources)
return 0;
exit(E_CONFIG_INVALID);
}
if (do_up) {
rv = do_proxy_conn_up(ctx);
if (!rv)
rv = do_proxy_conn_plugins(ctx);
}
else
rv = do_proxy_conn_down(ctx);
return rv;
}
static int adm_proxy_up(struct cfg_ctx *ctx)
{
return check_proxy(ctx, 1);
}
static int adm_proxy_down(struct cfg_ctx *ctx)
{
return check_proxy(ctx, 0);
}
/* The "main" loop iterates over resources.
* This "sorts" the drbdsetup commands to bring those up
* so we will later first create all objects,
* then attach all local disks,
* adjust various settings,
* and then configure the network part */
static int adm_up(struct cfg_ctx *ctx)
{
static char *current_res_name;
if (!current_res_name || strcmp(current_res_name, ctx->res->name)) {
free(current_res_name);
current_res_name = strdup(ctx->res->name);
schedule_deferred_cmd(adm_new_resource, ctx, "new-resource", CFG_PREREQ);
schedule_deferred_cmd(adm_connect, ctx, "connect", CFG_NET);
}
schedule_deferred_cmd(adm_new_minor, ctx, "new-minor", CFG_PREREQ);
schedule_deferred_cmd(adm_attach, ctx, "attach", CFG_DISK);
return 0;
}
/* The stacked-timeouts switch in the startup sections allows us
to enforce the use of the specified timeouts instead the use
of a sane value. Should only be used if the third node should
never become primary. */
static int adm_wait_c(struct cfg_ctx *ctx)
{
struct d_resource *res = ctx->res;
struct d_volume *vol = ctx->vol;
char *argv[MAX_ARGS];
struct d_option *opt;
int argc = 0, rv;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = "wait-connect";
ssprintf(argv[NA(argc)], "%d", vol->device_minor);
if (is_drbd_top && !res->stacked_timeouts) {
unsigned long timeout = 20;
if ((opt = find_opt(res->net_options, "connect-int"))) {
timeout = strtoul(opt->value, NULL, 10);
// one connect-interval? two?
timeout *= 2;
}
argv[argc++] = "--wfc-timeout";
ssprintf(argv[argc], "%lu", timeout);
argc++;
} else {
opt = res->startup_options;
make_options(opt);
}
argv[NA(argc)] = 0;
rv = m_system_ex(argv, SLEEPS_FOREVER, res->name);
return rv;
}
int ctx_by_minor(struct cfg_ctx *ctx, const char *id)
{
struct d_resource *res, *t;
struct d_volume *vol;
unsigned int mm;
mm = minor_by_id(id);
if (mm == -1U)
return -ENOENT;
for_each_resource(res, t, config) {
if (res->ignore)
continue;
for_each_volume(vol, res->me->volumes) {
if (mm == vol->device_minor) {
is_drbd_top = res->stacked;
ctx->res = res;
ctx->vol = vol;
return 0;
}
}
}
return -ENOENT;
}
struct d_resource *res_by_name(const char *name)
{
struct d_resource *res, *t;
for_each_resource(res, t, config) {
if (strcmp(name, res->name) == 0)
return res;
}
return NULL;
}
struct d_volume *volume_by_vnr(struct d_volume *volumes, int vnr)
{
struct d_volume *vol;
for_each_volume(vol, volumes)
if (vnr == vol->vnr)
return vol;
return NULL;
}
int ctx_by_name(struct cfg_ctx *ctx, const char *id)
{
struct d_resource *res, *t;
struct d_volume *vol;
char *name = strdupa(id);
char *vol_id = strchr(name, '/');
unsigned vol_nr = ~0U;
if (vol_id) {
*vol_id++ = '\0';
vol_nr = m_strtoll(vol_id, 0);
}
for_each_resource(res, t, config) {
if (res->ignore)
continue;
if (strcmp(name, res->name) == 0)
break;
}
if (!res)
return -ENOENT;
if (!vol_id) {
/* We could assign implicit volumes here.
* But that broke "drbdadm up specific-resource".
*/
ctx->res = res;
ctx->vol = NULL;
return 0;
}
vol = volume_by_vnr(res->me->volumes, vol_nr);
if (vol) {
ctx->res = res;
ctx->vol = vol;
return 0;
}
return -ENOENT;
}
int ctx_set_implicit_volume(struct cfg_ctx *ctx)
{
struct d_volume *vol, *v = NULL;
int volumes = 0;
if (ctx->vol || !ctx->res)
return 0;
if (!ctx->res->me) {
return 0;
}
for_each_volume(vol, ctx->res->me->volumes) {
volumes++;
v = vol;
}
if (volumes == 1)
ctx->vol = v;
return volumes;
}
/* In case a child exited, or exits, its return code is stored as
negative number in the pids[i] array */
static int childs_running(pid_t * pids, int opts)
{
int i = 0, wr, rv = 0, status;
int N = nr_volumes[is_drbd_top ? STACKED : NORMAL];
for (i = 0; i < N; i++) {
if (pids[i] <= 0)
continue;
wr = waitpid(pids[i], &status, opts);
if (wr == -1) { // Wait error.
if (errno == ECHILD) {
printf("No exit code for %d\n", pids[i]);
pids[i] = 0; // Child exited before ?
continue;
}
perror("waitpid");
exit(E_EXEC_ERROR);
}
if (wr == 0)
rv = 1; // Child still running.
if (wr > 0) {
pids[i] = 0;
if (WIFEXITED(status))
pids[i] = -WEXITSTATUS(status);
if (WIFSIGNALED(status))
pids[i] = -1000;
}
}
return rv;
}
static void kill_childs(pid_t * pids)
{
int i;
int N = nr_volumes[is_drbd_top ? STACKED : NORMAL];
for (i = 0; i < N; i++) {
if (pids[i] <= 0)
continue;
kill(pids[i], SIGINT);
}
}
/*
returns:
-1 ... all childs terminated
0 ... timeout expired
1 ... a string was read
*/
int gets_timeout(pid_t * pids, char *s, int size, int timeout)
{
int pr, rr, n = 0;
struct pollfd pfd;
if (s) {
pfd.fd = fileno(stdin);
pfd.events = POLLIN | POLLHUP | POLLERR | POLLNVAL;
n = 1;
}
redo_without_fd:
if (!childs_running(pids, WNOHANG)) {
pr = -1;
goto out;
}
do {
pr = poll(&pfd, n, timeout);
if (pr == -1) { // Poll error.
if (errno == EINTR) {
if (childs_running(pids, WNOHANG))
continue;
goto out; // pr = -1 here.
}
perror("poll");
exit(E_EXEC_ERROR);
}
} while (pr == -1);
if (pr == 1) { // Input available.
rr = read(fileno(stdin), s, size - 1);
if (rr == -1) {
perror("read");
exit(E_EXEC_ERROR);
} else if (size > 1 && rr == 0) {
/* WTF. End-of-file... avoid busy loop. */
s[0] = 0;
n = 0;
goto redo_without_fd;
}
s[rr] = 0;
}
out:
return pr;
}
static char *get_opt_val(struct d_option *base, const char *name, char *def)
{
while (base) {
if (!strcmp(base->name, name)) {
return base->value;
}
base = base->next;
}
return def;
}
static int check_exit_codes(pid_t * pids)
{
struct d_resource *res, *t;
int i = 0, rv = 0;
for_each_resource(res, t, config) {
if (res->ignore)
continue;
if (is_drbd_top != res->stacked)
continue;
if (pids[i] == -5 || pids[i] == -1000) {
pids[i] = 0;
}
if (pids[i] == -20)
rv = 20;
i++;
}
return rv;
}
static int adm_wait_ci(struct cfg_ctx *ctx)
{
struct d_resource *res, *t;
char *argv[20], answer[40];
pid_t *pids;
struct d_option *opt;
int rr, wtime, argc, i = 0;
time_t start;
int saved_stdin, saved_stdout, fd;
int N;
struct sigaction so, sa;
int have_tty = 1;
saved_stdin = -1;
saved_stdout = -1;
if (no_tty) {
err("WARN: stdin/stdout is not a TTY; using /dev/console");
fprintf(stdout,
"WARN: stdin/stdout is not a TTY; using /dev/console");
saved_stdin = dup(fileno(stdin));
if (saved_stdin == -1)
perror("dup(stdin)");
saved_stdout = dup(fileno(stdout));
if (saved_stdin == -1)
perror("dup(stdout)");
fd = open("/dev/console", O_RDONLY);
if (fd == -1) {
perror("open('/dev/console, O_RDONLY)");
have_tty = 0;
} else {
dup2(fd, fileno(stdin));
fd = open("/dev/console", O_WRONLY);
if (fd == -1)
perror("open('/dev/console, O_WRONLY)");
dup2(fd, fileno(stdout));
}
}
sa.sa_handler = chld_sig_hand;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, &so);
N = nr_volumes[is_drbd_top ? STACKED : NORMAL];
pids = alloca(N * sizeof(pid_t));
/* alloca can not fail, it can "only" overflow the stack :)
* but it needs to be initialized anyways! */
memset(pids, 0, N * sizeof(pid_t));
for_each_resource(res, t, config) {
struct d_volume *vol;
if (res->ignore)
continue;
if (is_drbd_top != res->stacked)
continue;
for_each_volume(vol, res->me->volumes) {
/* ctx is not used */
argc = 0;
argv[NA(argc)] = drbdsetup;
argv[NA(argc)] = "wait-connect";
ssprintf(argv[NA(argc)], "%u", vol->device_minor);
opt = res->startup_options;
make_options(opt);
argv[NA(argc)] = 0;
m__system(argv, RETURN_PID, res->name, &pids[i++], NULL, NULL);
}
}
wtime = global_options.dialog_refresh ? : -1;
start = time(0);
for (i = 0; i < 10; i++) {
// no string, but timeout
rr = gets_timeout(pids, 0, 0, 1 * 1000);
if (rr < 0)
break;
putchar('.');
fflush(stdout);
check_exit_codes(pids);
}
if (rr == 0) {
/* track a "yes", as well as ctrl-d and ctrl-c,
* in case our tty is stuck in "raw" mode, and
* we get it one character a time (-icanon) */
char yes_string[] = "yes\n";
char *yes_expect = yes_string;
int ctrl_c_count = 0;
int ctrl_d_count = 0;
/* Just in case, if plymouth or usplash is running,
* tell them to step aside.
* Also try to force canonical tty mode. */
printf
("\n***************************************************************\n"
" DRBD's startup script waits for the peer node(s) to appear.\n"
" - If this node was already a degraded cluster before the\n"
" reboot, the timeout is %s seconds. [degr-wfc-timeout]\n"
" - If the peer was available before the reboot, the timeout\n"
" is %s seconds. [wfc-timeout]\n"
" (These values are for resource '%s'; 0 sec -> wait forever)\n",
get_opt_val(config->startup_options, "degr-wfc-timeout",
"0"), get_opt_val(config->startup_options,
"wfc-timeout", "0"),
config->name);
if (!have_tty) {
printf(" To abort waiting for DRBD connections, kill this process: kill %u\n", getpid());
fflush(stdout);
/* wait untill killed, or all drbdsetup children have finished. */
do {
rr = poll(NULL, 0, -1);
if (rr == -1) {
if (errno == EINTR) {
if (childs_running(pids, WNOHANG))
continue;
break;
}
perror("poll");
exit(E_EXEC_ERROR);
}
} while (rr != -1);
kill_childs(pids);
childs_running(pids, 0);
check_exit_codes(pids);
return 0;
}
if (system("exec > /dev/null 2>&1; plymouth quit ; usplash_write QUIT ; "
"stty echo icanon icrnl"))
/* Ignore return value. Cannot do anything about it anyways. */;
printf(" To abort waiting enter 'yes' [ -- ]: ");
do {
printf("\e[s\e[31G[%4d]:\e[u", (int)(time(0) - start)); // Redraw sec.
fflush(stdout);
rr = gets_timeout(pids, answer, 40, wtime * 1000);
check_exit_codes(pids);
if (rr != 1)
continue;
/* If our tty is in "sane" or "canonical" mode,
* we get whole lines.
* If it still is in "raw" mode, even though we
* tried to set ICANON above, possibly some other
* "boot splash thingy" is in operation.
* We may be lucky to get single characters.
* If a sysadmin sees things stuck during boot,
* I expect that ctrl-c or ctrl-d will be one
* of the first things that are tried.
* In raw mode, we get these characters directly.
* But I want them to try that three times ;)
*/
if (answer[0] && answer[1] == 0) {
if (answer[0] == '\3')
++ctrl_c_count;
if (answer[0] == '\4')
++ctrl_d_count;
if (yes_expect && answer[0] == *yes_expect)
++yes_expect;
else if (answer[0] == '\n')
yes_expect = yes_string;
else
yes_expect = NULL;
}
if (!strcmp(answer, "yes\n") ||
(yes_expect && *yes_expect == '\0') ||
ctrl_c_count >= 3 ||
ctrl_d_count >= 3) {
kill_childs(pids);
childs_running(pids, 0);
check_exit_codes(pids);
break;
}
printf(" To abort waiting enter 'yes' [ -- ]:");
} while (rr != -1);
printf("\n");
}
if (saved_stdin != -1) {
dup2(saved_stdin, fileno(stdin));
dup2(saved_stdout, fileno(stdout));
}
return 0;
}
static void print_cmds(int level)
{
size_t i;
int j = 0;
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
if (cmds[i].show_in_usage != level)
continue;
if (j++ % 2) {
printf("%-35s\n", cmds[i].name);
} else {
printf(" %-35s", cmds[i].name);
}
}
if (j % 2)
printf("\n");
}
static int hidden_cmds(struct cfg_ctx *ignored __attribute((unused)))
{
printf("\nThese additional commands might be useful for writing\n"
"nifty shell scripts around drbdadm:\n\n");
print_cmds(2);
printf("\nThese commands are used by the kernel part of DRBD to\n"
"invoke user mode helper programs:\n\n");
print_cmds(3);
printf
("\nThese commands ought to be used by experts and developers:\n\n");
print_cmds(4);
printf("\n");
exit(0);
}
static void field_to_option(const struct field_def *field, struct option *option)
{
option->name = field->name;
option->has_arg = field->argument_is_optional ?
optional_argument : required_argument;
option->flag = NULL;
option->val = 257;
}
static void print_option(struct option *opt)
{
if (opt->has_arg == required_argument) {
printf(" --%s=...", opt->name);
if (opt->val > 1 && opt->val < 256)
printf(", -%c ...", opt->val);
printf("\n");
} else if (opt->has_arg == optional_argument) {
printf(" --%s[=...]", opt->name);
if (opt->val > 1 && opt->val < 256)
printf(", -%c...", opt->val);
printf("\n");
} else {
printf(" --%s", opt->name);
if (opt->val > 1 && opt->val < 256)
printf(", -%c", opt->val);
printf("\n");
}
}
void print_usage_and_exit(struct adm_cmd *cmd, const char *addinfo, int status)
{
struct option *opt;
printf("\nUSAGE: %s %s [OPTION...] {all|RESOURCE...}\n\n"
"GENERAL OPTIONS:\n", progname, cmd ? cmd->name : "COMMAND");
for (opt = general_admopt; opt->name; opt++)
print_option(opt);
if (cmd && cmd->drbdsetup_ctx) {
const struct field_def *field;
printf("\nOPTIONS FOR %s:\n", cmd->name);
for (field = cmd->drbdsetup_ctx->fields; field->name; field++) {
struct option opt;
field_to_option(field, &opt);
print_option(&opt);
}
}
if (!cmd) {
printf("\nCOMMANDS:\n");
print_cmds(1);
}
printf("\nVersion: " PACKAGE_VERSION " (api:%d)\n%s\n",
API_VERSION, drbd_buildtag());
if (addinfo)
printf("\n%s\n", addinfo);
exit(status);
}
void verify_ips(struct d_resource *res)
{
if (global_options.disable_ip_verification)
return;
if (dry_run == 1 || do_verify_ips == 0)
return;
if (res->ignore)
return;
if (res->stacked && !is_drbd_top)
return;
if (!have_ip(res->me->address_family, res->me->address)) {
ENTRY e, *ep;
e.key = e.data = ep = NULL;
m_asprintf(&e.key, "%s:%s", res->me->address, res->me->port);
hsearch_r(e, FIND, &ep, &global_htable);
err("%s: in resource %s, on %s:\n\t"
"IP %s not found on this host.\n",
ep ? (char *)ep->data : res->config_file,
res->name, names_to_str(res->me->on_hosts),
res->me->address);
if (INVALID_IP_IS_INVALID_CONF)
config_valid = 0;
}
}
static char *conf_file[] = {
DRBD_CONFIG_DIR "/drbd-84.conf",
DRBD_CONFIG_DIR "/drbd-83.conf",
DRBD_CONFIG_DIR "/drbd-82.conf",
DRBD_CONFIG_DIR "/drbd-08.conf",
DRBD_CONFIG_DIR "/drbd.conf",
0
};
int sanity_check_abs_cmd(char *cmd_name)
{
struct stat sb;
if (stat(cmd_name, &sb)) {
/* If stat fails, just ignore this sanity check,
* we are still iterating over $PATH probably. */
return 0;
}
if (!(sb.st_mode & S_ISUID) || sb.st_mode & S_IXOTH || sb.st_gid == 0) {
static int did_header = 0;
if (!did_header)
err(
"WARN:\n"
" You are using the 'drbd-peer-outdater' as fence-peer program.\n"
" If you use that mechanism the dopd heartbeat plugin program needs\n"
" to be able to call drbdsetup and drbdmeta with root privileges.\n\n"
" You need to fix this with these commands:\n");
did_header = 1;
err(
" chgrp haclient %s\n"
" chmod o-x %s\n"
" chmod u+s %s\n\n", cmd_name, cmd_name, cmd_name);
}
return 1;
}
void sanity_check_cmd(char *cmd_name)
{
char *path, *pp, *c;
char abs_path[100];
if (strchr(cmd_name, '/')) {
sanity_check_abs_cmd(cmd_name);
} else {
path = pp = c = strdup(getenv("PATH"));
while (1) {
c = strchr(pp, ':');
if (c)
*c = 0;
snprintf(abs_path, 100, "%s/%s", pp, cmd_name);
if (sanity_check_abs_cmd(abs_path))
break;
if (!c)
break;
c++;
if (!*c)
break;
pp = c;
}
free(path);
}
}
/* if the config file is not readable by haclient,
* dopd cannot work.
* NOTE: we assume that any gid != 0 will be the group dopd will run as,
* typically haclient. */
void sanity_check_conf(char *c)
{
struct stat sb;
/* if we cannot stat the config file,
* we have other things to worry about. */
if (stat(c, &sb))
return;
/* permissions are funny: if it is world readable,
* but not group readable, and it belongs to my group,
* I am denied access.
* For the file to be readable by dopd (hacluster:haclient),
* it is not enough to be world readable. */
/* ok if world readable, and NOT group haclient (see NOTE above) */
if (sb.st_mode & S_IROTH && sb.st_gid == 0)
return;
/* ok if group readable, and group haclient (see NOTE above) */
if (sb.st_mode & S_IRGRP && sb.st_gid != 0)
return;
err(
"WARN:\n"
" You are using the 'drbd-peer-outdater' as fence-peer program.\n"
" If you use that mechanism the dopd heartbeat plugin program needs\n"
" to be able to read the drbd.config file.\n\n"
" You need to fix this with these commands:\n"
" chgrp haclient %s\n" " chmod g+r %s\n\n", c, c);
}
void sanity_check_perm()
{
static int checked = 0;
if (checked)
return;
sanity_check_cmd(drbdsetup);
sanity_check_cmd(drbdmeta);
sanity_check_conf(config_file);
checked = 1;
}
void validate_resource(struct d_resource *res, enum pp_flags flags)
{
struct d_option *opt, *next;
struct d_name *bpo;
/* there may be more than one "resync-after" statement,
* see commit 89cd0585 */
opt = res->disk_options;
while ((opt = find_opt(opt, "resync-after"))) {
struct d_resource *rs_after_res = res_by_name(opt->value);
next = opt->next;
if (rs_after_res == NULL ||
(rs_after_res->ignore && !(flags & MATCH_ON_PROXY))) {
err(
"%s:%d: in resource %s:\n\tresource '%s' mentioned in "
"'resync-after' option is not known%s.\n",
res->config_file, res->start_line, res->name,
opt->value,
rs_after_res ? " on this host" : "");
/* Non-fatal if run from some script.
* When deleting resources, it is an easily made
* oversight to leave references to the deleted
* resources in resync-after statements. Don't fail on
* every pacemaker-induced action, as it would
* ultimately lead to all nodes committing suicide. */
if (no_tty)
res->disk_options = del_opt(res->disk_options, opt);
else
config_valid = 0;
}
opt = next;
}
if (res->ignore)
return;
if (!res->me) {
err(
"%s:%d: in resource %s:\n\tmissing section 'on %s { ... }'.\n",
res->config_file, res->start_line, res->name,
nodeinfo.nodename);
config_valid = 0;
}
// need to verify that in the discard-node-nodename options only known
// nodenames are mentioned.
if ((opt = find_opt(res->net_options, "after-sb-0pri"))) {
if (!strncmp(opt->value, "discard-node-", 13)) {
if (res->peer &&
!name_in_names(opt->value + 13, res->peer->on_hosts)
&& !name_in_names(opt->value + 13,
res->me->on_hosts)) {
err(
"%s:%d: in resource %s:\n\t"
"the nodename in the '%s' option is "
"not known.\n\t"
"valid nodenames are: '%s %s'.\n",
res->config_file, res->start_line,
res->name, opt->value,
names_to_str(res->me->on_hosts),
names_to_str(res->peer->on_hosts));
config_valid = 0;
}
}
}
if ((opt = find_opt(res->handlers, "fence-peer"))) {
if (strstr(opt->value, "drbd-peer-outdater"))
sanity_check_perm();
}
opt = find_opt(res->net_options, "allow-two-primaries");
if (name_in_names("both", res->become_primary_on) && opt == NULL) {
err(
"%s:%d: in resource %s:\n"
"become-primary-on is set to both, but allow-two-primaries "
"is not set.\n", res->config_file, res->start_line,
res->name);
config_valid = 0;
}
if (!res->peer)
set_peer_in_resource(res, 0);
if (res->peer
&& ((res->me->proxy == NULL) != (res->peer->proxy == NULL))) {
err(
"%s:%d: in resource %s:\n\t"
"Either both 'on' sections must contain a proxy subsection, or none.\n",
res->config_file, res->start_line, res->name);
config_valid = 0;
}
for (bpo = res->become_primary_on; bpo; bpo = bpo->next) {
if (res->peer &&
!name_in_names(bpo->name, res->me->on_hosts) &&
!name_in_names(bpo->name, res->peer->on_hosts) &&
strcmp(bpo->name, "both")) {
err(
"%s:%d: in resource %s:\n\t"
"become-primary-on contains '%s', which is not named with the 'on' sections.\n",
res->config_file, res->start_line, res->name,
bpo->name);
config_valid = 0;
}
}
}
static void global_validate_maybe_expand_die_if_invalid(int expand, enum pp_flags flags)
{
struct d_resource *res, *tmp;
for_each_resource(res, tmp, config) {
validate_resource(res, flags);
if (!config_valid)
exit(E_CONFIG_INVALID);
if (expand) {
convert_after_option(res);
convert_discard_opt(res);
}
if (!config_valid)
exit(E_CONFIG_INVALID);
}
}
/*
* returns a pointer to an malloced area that contains
* an absolute, canonical, version of path.
* aborts if any allocation or syscall fails.
* return value should be free()d, once no longer needed.
*/
char *canonify_path(char *path)
{
int cwd_fd = -1;
char *last_slash;
char *tmp;
char *that_wd;
char *abs_path;
if (!path || !path[0]) {
err("cannot canonify an empty path\n");
exit(E_USAGE);
}
tmp = strdupa(path);
last_slash = strrchr(tmp, '/');
if (last_slash) {
*last_slash++ = '\0';
cwd_fd = open(".", O_RDONLY | O_CLOEXEC);
if (cwd_fd < 0) {
err("open(\".\") failed: %m\n");
exit(E_USAGE);
}
if (chdir(tmp)) {
err("chdir(\"%s\") failed: %m\n", tmp);
exit(E_USAGE);
}
} else {
last_slash = tmp;
}
that_wd = getcwd(NULL, 0);
if (!that_wd) {
err("getcwd() failed: %m\n");
exit(E_USAGE);
}
if (!strcmp("/", that_wd))
m_asprintf(&abs_path, "/%s", last_slash);
else
m_asprintf(&abs_path, "%s/%s", that_wd, last_slash);
free(that_wd);
if (cwd_fd >= 0) {
if (fchdir(cwd_fd) < 0) {
err("fchdir() failed: %m\n");
exit(E_USAGE);
}
close(cwd_fd);
}
return abs_path;
}
void assign_command_names_from_argv0(char **argv)
{
struct cmd_helper {
char *name;
char **var;
};
static struct cmd_helper helpers[] = {
{"drbdsetup-84", &drbdsetup},
{"drbdmeta", &drbdmeta},
{"drbd-proxy-ctl", &drbd_proxy_ctl},
{"drbdadm-83", &drbdadm_83},
{NULL, NULL}
};
struct cmd_helper *c;
/* in case drbdadm is called with an absolute or relative pathname
* look for the drbdsetup binary in the same location,
* otherwise, just let execvp sort it out... */
if ((progname = strrchr(argv[0], '/')) == NULL) {
progname = argv[0];
for (c = helpers; c->name; ++c)
*(c->var) = strdup(c->name);
} else {
size_t len_dir, l;
++progname;
len_dir = progname - argv[0];
for (c = helpers; c->name; ++c) {
l = len_dir + strlen(c->name) + 1;
*(c->var) = malloc(l);
if (*(c->var)) {
strncpy(*(c->var), argv[0], len_dir);
strcpy(*(c->var) + len_dir, c->name);
if (access(*(c->var), X_OK))
strcpy(*(c->var), c->name); /* see add_lib_drbd_to_path() */
}
}
/* for pretty printing, truncate to basename */
argv[0] = progname;
}
}
static void recognize_all_drbdsetup_options(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
const struct adm_cmd *cmd = &cmds[i];
const struct field_def *field;
if (!cmd->drbdsetup_ctx)
continue;
for (field = cmd->drbdsetup_ctx->fields; field->name; field++) {
struct option opt;
int n;
field_to_option(field, &opt);
for (n = 0; admopt[n].name; n++) {
if (!strcmp(admopt[n].name, field->name)) {
if (admopt[n].val == 257)
assert (admopt[n].has_arg == opt.has_arg);
else {
err("Warning: drbdsetup %s option --%s "
"can only be passed as -W--%s\n",
cmd->name, admopt[n].name, admopt[n].name);
goto skip;
}
}
}
if (admopt == general_admopt) {
admopt = malloc((n + 2) * sizeof(*admopt));
memcpy(admopt, general_admopt, (n + 1) * sizeof(*admopt));
} else
admopt = realloc(admopt, (n + 2) * sizeof(*admopt));
memcpy(&admopt[n+1], &admopt[n], sizeof(*admopt));
admopt[n] = opt;
skip:
/* dummy statement required because of label */ ;
}
}
}
struct adm_cmd *find_cmd(char *cmdname);
int parse_options(int argc, char **argv, struct adm_cmd **cmd, char ***resource_names)
{
const char *optstring = make_optstring(admopt);
int longindex, first_arg_index;
int i;
*cmd = NULL;
*resource_names = malloc(sizeof(char *));
(*resource_names)[0] = NULL;
opterr = 1;
optind = 0;
while (1) {
int c;
c = getopt_long(argc, argv, optstring, admopt, &longindex);
if (c == -1)
break;
switch (c) {
case 257: /* drbdsetup option */
{
struct option *option = &admopt[longindex];
char *opt;
int len;
len = strlen(option->name) + 2;
if (optarg)
len += 1 + strlen(optarg);
opt = malloc(len + 1);
if (optarg)
sprintf(opt, "--%s=%s", option->name, optarg);
else
sprintf(opt, "--%s", option->name);
add_setup_option(false, opt);
}
break;
case 'S':
is_drbd_top = 1;
break;
case 'v':
verbose++;
break;
case 'd':
dry_run++;
break;
case 'c':
if (!strcmp(optarg, "-")) {
yyin = stdin;
if (asprintf(&config_file, "STDIN") < 0) {
err("asprintf(config_file): %m\n");
return 20;
}
config_from_stdin = 1;
} else {
yyin = fopen(optarg, "r");
if (!yyin) {
err("Can not open '%s'.\n.", optarg);
exit(E_EXEC_ERROR);
}
if (asprintf(&config_file, "%s", optarg) < 0) {
err("asprintf(config_file): %m\n");
return 20;
}
}
break;
case 't':
config_test = optarg;
break;
case 's':
{
char *pathes[2];
pathes[0] = optarg;
pathes[1] = 0;
find_drbdcmd(&drbdsetup, pathes);
}
break;
case 'm':
{
char *pathes[2];
pathes[0] = optarg;
pathes[1] = 0;
find_drbdcmd(&drbdmeta, pathes);
}
break;
case 'p':
{
char *pathes[2];
pathes[0] = optarg;
pathes[1] = 0;
find_drbdcmd(&drbd_proxy_ctl, pathes);
}
break;
case 'n':
{
char *c;
int shell_var_name_ok = 1;
for (c = optarg; *c && shell_var_name_ok; c++) {
switch (*c) {
case 'a'...'z':
case 'A'...'Z':
case '0'...'9':
case '_':
break;
default:
shell_var_name_ok = 0;
}
}
if (shell_var_name_ok)
sh_varname = optarg;
else
err("ignored --sh-varname=%s: "
"contains suspect characters, allowed set is [a-zA-Z0-9_]\n",
optarg);
}
break;
case 'V':
printf("DRBDADM_BUILDTAG=%s\n", shell_escape(drbd_buildtag()));
printf("DRBDADM_API_VERSION=%u\n", API_VERSION);
printf("DRBD_KERNEL_VERSION_CODE=0x%06x\n", version_code_kernel());
printf("DRBDADM_VERSION_CODE=0x%06x\n", version_code_userland());
printf("DRBDADM_VERSION=%s\n", shell_escape(PACKAGE_VERSION));
exit(0);
break;
case 'P':
connect_to_host = optarg;
break;
case 'W':
add_setup_option(true, optarg);
break;
case 'h':
help = true;
break;
case '?':
goto help;
}
}
first_arg_index = optind;
for (; optind < argc; optind++) {
optarg = argv[optind];
if (*cmd) {
int n;
ensure_sanity_of_res_name(optarg);
for (n = 0; (*resource_names)[n]; n++)
/* do nothing */ ;
*resource_names = realloc(*resource_names,
(n + 2) * sizeof(char *));
(*resource_names)[n++] = optarg;
(*resource_names)[n] = NULL;
} else if (!strcmp(optarg, "help"))
help = true;
else {
*cmd = find_cmd(optarg);
if (!*cmd) {
/* Passing drbdsetup options like this is discouraged! */
add_setup_option(true, optarg);
}
}
}
if (help)
print_usage_and_exit(*cmd, NULL, 0);
if (*cmd == NULL) {
if (first_arg_index < argc) {
err("%s: Unknown command '%s'\n", progname, argv[first_arg_index]);
return E_USAGE;
}
print_usage_and_exit(*cmd, "No command specified", E_USAGE);
}
if (setup_options) {
bool is_create_md = (*cmd)->name && !strcmp((*cmd)->name, "create-md");
/*
* The drbdsetup options are command specific. Make sure that only
* setup options that this command recognizes are used.
*/
for (i = 0; setup_options[i].option; i++) {
const struct field_def *field;
const char *option;
int len;
if (setup_options[i].explicit)
continue;
option = setup_options[i].option;
for (len = 0; option[len]; len++)
if (option[len] == '=')
break;
field = NULL;
if (option[0] == '-' && option[1] == '-' && (*cmd)->drbdsetup_ctx) {
for (field = (*cmd)->drbdsetup_ctx->fields; field->name; field++) {
if (strlen(field->name) == len - 2 &&
!strncmp(option + 2, field->name, len - 2))
break;
}
if (!field->name)
field = NULL;
}
if (!field && !is_create_md) {
err("%s: unrecognized option '%.*s'\n", progname, len, option);
goto help;
}
}
}
return 0;
help:
if (*cmd)
err("try '%s help %s'\n", progname, (*cmd)->name);
else
err("try '%s help'\n", progname);
return E_USAGE;
}
struct adm_cmd *find_cmd(char *cmdname)
{
struct adm_cmd *cmd = NULL;
unsigned int i;
if (!strcmp("hidden-commands", cmdname)) {
// before parsing the configuration file...
hidden_cmds(NULL);
exit(0);
}
/* R_PRIMARY / R_SECONDARY is not a state, but a role. Whatever that
* means, actually. But anyways, we decided to start using _role_ as
* the terminus of choice, and deprecate "state". */
substitute_deprecated_cmd(&cmdname, "state", "role");
/* "outdate-peer" got renamed to fence-peer,
* it is not required to actually outdate the peer,
* depending on situation it may be sufficient to power-reset it
* or do some other fencing action, or even call out to "meatware".
* The name of the handler should not imply something that is not done. */
substitute_deprecated_cmd(&cmdname, "outdate-peer", "fence-peer");
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
if (!strcmp(cmds[i].name, cmdname)) {
cmd = cmds + i;
break;
}
}
return cmd;
}
char *config_file_from_arg(char *arg)
{
char *f;
int minor = minor_by_id(arg);
if (minor >= 0) {
f = lookup_minor(minor);
if (!f) {
err("Don't know which config file belongs to minor %d, trying default ones...\n", minor);
return NULL;
}
} else {
f = lookup_resource(arg);
if (!f) {
err("Don't know which config file belongs to resource %s, trying default ones...\n", arg);
return NULL;
}
}
yyin = fopen(f, "r");
if (yyin == NULL) {
err("Couldn't open file %s for reading, reason: %m\ntrying default config file...\n", config_file);
return NULL;
}
return f;
}
void assign_default_config_file(void)
{
int i;
for (i = 0; conf_file[i]; i++) {
yyin = fopen(conf_file[i], "r");
if (yyin) {
config_file = conf_file[i];
break;
}
}
if (!config_file) {
err("Can not open '%s': %m\n", conf_file[i - 1]);
exit(E_CONFIG_INVALID);
}
}
void count_resources(void)
{
struct d_resource *res, *tmp;
struct d_volume *vol;
number_of_minors = 0;
for_each_resource(res, tmp, config) {
if (res->ignore) {
nr_resources[IGNORED]++;
/* How can we count ignored volumes?
* Do we want to? */
continue;
} else if (res->stacked)
nr_resources[STACKED]++;
else
nr_resources[NORMAL]++;
for_each_volume(vol, res->me->volumes) {
number_of_minors++;
if (res->stacked)
nr_volumes[STACKED]++;
/* res->ignored won't come here */
else
nr_volumes[NORMAL]++;
}
}
}
void die_if_no_resources(void)
{
if (!is_drbd_top && nr_resources[IGNORED] > 0 && nr_resources[NORMAL] == 0) {
err("WARN: no normal resources defined for this host (%s)!?\n"
"Misspelled name of the local machine with the 'on' keyword ?\n",
nodeinfo.nodename);
exit(E_USAGE);
}
if (!is_drbd_top && nr_resources[NORMAL] == 0) {
err("WARN: no normal resources defined for this host (%s)!?\n", nodeinfo.nodename);
exit(E_USAGE);
}
if (is_drbd_top && nr_resources[STACKED] == 0) {
err("WARN: nothing stacked for this host (%s), "
"nothing to do in stacked mode!\n", nodeinfo.nodename);
exit(E_USAGE);
}
}
void print_dump_xml_header(void)
{
printf("<config file=\"%s\">\n", config_save);
++indent;
dump_global_info_xml();
dump_common_info_xml();
}
void print_dump_header(void)
{
printf("# %s\n", config_save);
dump_global_info();
dump_common_info();
}
int main(int argc, char **argv)
{
size_t i;
int rv = 0, r;
struct adm_cmd *cmd = NULL;
char **resource_names = NULL;
struct d_resource *res, *tmp;
char *env_drbd_nodename = NULL;
int is_dump_xml;
int is_dump;
bool is_status;
struct cfg_ctx ctx = { .arg = NULL };
initialize_err();
yyin = NULL;
uname(&nodeinfo); /* FIXME maybe fold to lower case ? */
no_tty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));
env_drbd_nodename = getenv("__DRBD_NODE__");
if (env_drbd_nodename && *env_drbd_nodename) {
strncpy(nodeinfo.nodename, env_drbd_nodename,
sizeof(nodeinfo.nodename) - 1);
nodeinfo.nodename[sizeof(nodeinfo.nodename) - 1] = 0;
err("\n"
" found __DRBD_NODE__ in environment\n"
" PRETENDING that I am >>%s<<\n\n",
nodeinfo.nodename);
}
assign_command_names_from_argv0(argv);
if (drbdsetup == NULL || drbdmeta == NULL || drbd_proxy_ctl == NULL) {
err("could not strdup argv[0].\n");
exit(E_EXEC_ERROR);
}
if (!getenv("DRBD_DONT_WARN_ON_VERSION_MISMATCH"))
warn_on_version_mismatch();
maybe_exec_drbdadm_83(argv);
recognize_all_drbdsetup_options();
rv = parse_options(argc, argv, &cmd, &resource_names);
if (rv)
return rv;
if (config_test && !cmd->test_config) {
err("The --config-to-test (-t) option is only allowed "
"with the dump and sh-nop commands\n");
exit(E_USAGE);
}
do_verify_ips = cmd->verify_ips;
is_dump_xml = (cmd->function == adm_dump_xml);
is_dump = (is_dump_xml || cmd->function == adm_dump);
is_status = (cmd->function == adm_status);
if (!resource_names[0]) {
if (is_dump || is_status)
all_resources = 1;
else if (cmd->res_name_required)
print_usage_and_exit(cmd, "No resource names specified", E_USAGE);
} else if (resource_names[0]) {
if (!cmd->res_name_required)
err("This command will ignore resource names!\n");
else if (resource_names[1] && cmd->use_cached_config_file)
err("You should not use this command with multiple resources!\n");
}
if (!config_file && cmd->use_cached_config_file)
config_file = config_file_from_arg(resource_names[0]);
if (!config_file)
/* may exit if no config file can be used! */
assign_default_config_file();
/* for error-reporting reasons config_file may be re-assigned by adm_adjust,
* we need the current value for register_minor, though.
* save that. */
if (config_from_stdin)
config_save = config_file;
else
config_save = canonify_path(config_file);
my_parse();
if (config_test) {
char *saved_config_file = config_file;
char *saved_config_save = config_save;
config_file = config_test;
config_save = canonify_path(config_test);
fclose(yyin);
yyin = fopen(config_test, "r");
if (!yyin) {
err("Can not open '%s'.\n.", config_test);
exit(E_EXEC_ERROR);
}
my_parse();
config_file = saved_config_file;
config_save = saved_config_save;
}
if (!config_valid)
exit(E_CONFIG_INVALID);
post_parse(config, cmd->is_proxy_cmd ? MATCH_ON_PROXY : 0);
if (!is_dump || dry_run || verbose)
expand_common();
if (dry_run || config_from_stdin)
do_register = 0;
count_resources();
if (cmd->uc_dialog)
uc_node(global_options.usage_count);
ctx.arg = cmd->name;
if (cmd->res_name_required) {
if (config == NULL && !is_dump) {
err("no resources defined!\n");
exit(E_USAGE);
}
global_validate_maybe_expand_die_if_invalid(!is_dump,
cmd->is_proxy_cmd ? MATCH_ON_PROXY : 0);
if (!resource_names[0] || !strcmp(resource_names[0], "all")) {
/* either no resource arguments at all,
* but command is dump / dump-xml, so implicit "all",
* or an explicit "all" argument is given */
all_resources = 1;
if (!is_dump)
die_if_no_resources();
/* verify ips first, for all of them */
for_each_resource(res, tmp, config) {
verify_ips(res);
}
if (!config_valid)
exit(E_CONFIG_INVALID);
if (is_dump_xml)
print_dump_xml_header();
else if (is_dump)
print_dump_header();
for_each_resource(res, tmp, config) {
if (!is_dump && res->ignore)
continue;
if (!is_dump && is_drbd_top != res->stacked)
continue;
ctx.res = res;
ctx.vol = NULL;
r = call_cmd(cmd, &ctx, EXIT_ON_FAIL); /* does exit for r >= 20! */
/* this super positioning of return values is soo ugly
* anyone any better idea? */
if (r > rv)
rv = r;
}
if (is_dump_xml) {
--indent;
printf("</config>\n");
}
} else {
/* explicit list of resources to work on */
for (i = 0; resource_names[i]; i++) {
ctx.res = NULL;
ctx.vol = NULL;
ctx_by_name(&ctx, resource_names[i]);
if (!ctx.res)
ctx_by_minor(&ctx, resource_names[i]);
if (!ctx.res) {
err("'%s' not defined in your config (for this host).\n", resource_names[i]);
exit(E_USAGE);
}
if (!cmd->vol_id_required && !cmd->iterate_volumes && ctx.vol != NULL) {
if (ctx.vol->implicit)
ctx.vol = NULL;
else {
err("%s operates on whole resources, but you specified a specific volume!\n",
cmd->name);
exit(E_USAGE);
}
}
if (cmd->vol_id_required && !ctx.vol && ctx.res->me->volumes->implicit)
ctx.vol = ctx.res->me->volumes;
if (cmd->vol_id_required && !ctx.vol) {
err("%s requires a specific volume id, but none is specified.\n"
"Try '%s minor-<minor_number>' or '%s %s/<vnr>'\n",
cmd->name, cmd->name, cmd->name, resource_names[i]);
exit(E_USAGE);
}
if (ctx.res->ignore && !is_dump) {
err("'%s' ignored, since this host (%s) is not mentioned with an 'on' keyword.\n",
ctx.res->name, nodeinfo.nodename);
rv = E_USAGE;
continue;
}
if (is_drbd_top != ctx.res->stacked && !is_dump) {
err("'%s' is a %s resource, and not available in %s mode.\n",
ctx.res->name,
ctx.res->stacked ? "stacked" : "normal",
is_drbd_top ? "stacked" : "normal");
rv = E_USAGE;
continue;
}
verify_ips(ctx.res);
if (!is_dump && !config_valid)
exit(E_CONFIG_INVALID);
r = call_cmd(cmd, &ctx, EXIT_ON_FAIL); /* does exit for rv >= 20! */
if (r > rv)
rv = r;
}
}
} else { // Commands which do not need a resource name
/* no call_cmd, as that implies register_minor,
* which does not make sense for resource independent commands.
* It does also not need to iterate over volumes: it does not even know the resource. */
rv = cmd->function(&ctx);
if (rv >= 10) { /* why do we special case the "generic sh-*" commands? */
err("command %s exited with code %d\n", cmd->name, rv);
exit(rv);
}
}
r = run_deferred_cmds();
if (r > rv)
rv = r;
free_config(config);
free(resource_names);
if (admopt != general_admopt)
free(admopt);
return rv;
}
void yyerror(char *text)
{
err("%s:%d: %s\n", config_file, line, text);
exit(E_SYNTAX);
}