blob: 3120e0d0cac20c2535a3da6b1279f2dba98afc69 [file] [edit]
/*
* DRBD setup via genetlink
*
* This file is part of DRBD by Philipp Reisner and Lars Ellenberg.
*
* Copyright (C) 2001-2008, LINBIT Information Technologies GmbH.
* Copyright (C) 1999-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 <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <assert.h>
#include <stdarg.h>
#include <libgen.h>
#include <time.h>
#include <search.h>
#include <syslog.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#define EXIT_NOMEM 20
#define EXIT_NO_FAMILY 20
#define EXIT_SEND_ERR 20
#define EXIT_RECV_ERR 20
#define EXIT_TIMED_OUT 20
#define EXIT_NOSOCK 30
#define EXIT_THINKO 42
/* is_intentional is a boolean value we get via nl from kernel. if we use new
* utils and old kernel we don't get it, so we set this default, get kernel
* info, and then decide from the value if the kernel was new enough */
#define IS_INTENTIONAL_DEF 3
/*
* We are not using libnl,
* using its API for the few things we want to do
* ends up being almost as much lines of code as
* coding the necessary bits right here.
*/
#include "libgenl.h"
#include "drbd_nla.h"
#include <linux/drbd_config.h>
#include <linux/drbd_genl_api.h>
#include <linux/drbd_limits.h>
#include "drbdtool_common.h"
#include <linux/genl_magic_func.h>
#include "drbd_strings.h"
#include "registry.h"
#include "config.h"
#include "config_flags.h"
#include "wrap_printf.h"
#include "drbdsetup_colors.h"
char *progname;
/* for parsing of messages */
static struct nlattr *global_attrs[128];
/* there is an other table, nested_attr_tb, defined in genl_magic_func.h,
* which can be used after <struct>_from_attrs,
* to check for presence of struct fields. */
#define ntb(t) nested_attr_tb[__nla_type(t)]
#ifdef PRINT_NLMSG_LEN
/* I'm to lazy to check the maximum possible nlmsg length by hand */
int main(void)
{
static __u16 nla_attr_minlen[NLA_TYPE_MAX+1] __read_mostly = {
[NLA_U8] = sizeof(__u8),
[NLA_U16] = sizeof(__u16),
[NLA_U32] = sizeof(__u32),
[NLA_U64] = sizeof(__u64),
[NLA_NESTED] = NLA_HDRLEN,
};
int i;
int sum_total = 0;
#define LEN__(policy) do { \
int sum = 0; \
for (i = 0; i < ARRAY_SIZE(policy); i++) { \
sum += nla_total_size(policy[i].len ?: \
nla_attr_minlen[policy[i].type]); \
\
} \
sum += 4; \
sum_total += sum; \
printf("%-30s %4u [%4u]\n", \
#policy ":", sum, sum_total); \
} while (0)
#define LEN_(p) LEN__(p ## _nl_policy)
LEN_(disk_conf);
LEN_(syncer_conf);
LEN_(net_conf);
LEN_(set_role_parms);
LEN_(resize_parms);
LEN_(state_info);
LEN_(start_ov_parms);
LEN_(new_c_uuid_parms);
sum_total += sizeof(struct nlmsghdr) + sizeof(struct genlmsghdr)
+ sizeof(struct drbd_genlmsghdr);
printf("sum total inclusive hdr overhead: %4u\n", sum_total);
return 0;
}
#else
#ifndef AF_INET_SDP
#define AF_INET_SDP 27
#define PF_INET_SDP AF_INET_SDP
#endif
#define MULTIPLE_TIMEOUTS (-2)
/* pretty print helpers */
static int indent = 0;
#define INDENT_WIDTH 4
#define printI(fmt, args... ) printf("%*s" fmt,INDENT_WIDTH * indent,"" , ## args )
enum usage_type {
BRIEF,
FULL,
XML,
};
struct drbd_argument {
const char* name;
__u16 nla_type;
int (*convert_function)(struct drbd_argument *,
struct msg_buff *,
struct drbd_genlmsghdr *dhdr,
char *);
};
/* Configuration requests typically need a context to operate on.
* Possible keys are device minor/volume id (both fit in the drbd_genlmsghdr),
* the replication link (aka connection) name,
* and/or the replication group (aka resource) name */
enum cfg_ctx_key {
/* Only one of these can be present in a command: */
CTX_RESOURCE = 1,
CTX_PEER_NODE_ID = 2,
CTX_MINOR = 4,
CTX_VOLUME = 8,
CTX_MY_ADDR = 16,
CTX_PEER_ADDR = 32,
CTX_ALL = 64,
CTX_MULTIPLE_ARGUMENTS = 128,
/* To identify a connection, we use (resource_name, peer_node_id) */
CTX_PEER_NODE = CTX_RESOURCE | CTX_PEER_NODE_ID | CTX_MULTIPLE_ARGUMENTS,
CTX_PEER_DEVICE = CTX_PEER_NODE | CTX_VOLUME,
};
enum cfg_ctx_key ctx_next_arg(enum cfg_ctx_key *key)
{
enum cfg_ctx_key next_arg;
if (*key & CTX_MULTIPLE_ARGUMENTS) {
next_arg = *key & ~(*key - 1); /* the lowest set bit */
next_arg &= ~CTX_MULTIPLE_ARGUMENTS;
} else
next_arg = *key;
*key &= ~next_arg;
return next_arg;
}
const char *ctx_arg_string(enum cfg_ctx_key key, enum usage_type ut)
{
bool xml = (ut == XML);
switch(key) {
case CTX_RESOURCE:
return xml ? "resource" : "{resource}";
case CTX_PEER_NODE_ID:
return xml ? "peer_node_id" : "{peer_node_id}";
case CTX_MINOR:
return xml ? "minor" : "{minor}";
case CTX_VOLUME:
return xml ? "volume" : "{volume}";
case CTX_MY_ADDR:
return xml ? "local_addr" : "[local:][{af}:]{local_addr}[:{port}]";
case CTX_PEER_ADDR:
return xml ? "remote_addr" : "[peer:][{af}:]{remote_addr}[:{port}]";
case CTX_ALL:
return "all";
default:
assert(0);
}
return "unknown argument";
}
struct drbd_cmd {
const char* cmd;
enum cfg_ctx_key ctx_key;
int cmd_id;
int tla_id; /* top level attribute id */
int (*function)(struct drbd_cmd *, int, char **);
struct drbd_argument *drbd_args;
int (*show_function)(struct drbd_cmd*, struct genl_info *, void *u_ptr);
struct option *options;
bool missing_ok;
bool warn_on_missing;
bool continuous_poll;
bool set_defaults;
bool lockless;
struct context_def *ctx;
const char *summary;
};
// other functions
static int get_af_ssocks(int warn);
static void print_command_usage(struct drbd_cmd *cm, enum usage_type);
static void print_usage_and_exit(const char *addinfo)
__attribute__ ((noreturn));
static const char *resync_susp_str(struct peer_device_info *info);
static const char *intentional_diskless_str(struct device_info *info);
static const char *peer_intentional_diskless_str(struct peer_device_info *info);
// command functions
static int generic_config_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int down_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int generic_get_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int del_minor_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int del_resource_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int show_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int status_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int role_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int cstate_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int dstate_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int check_resize_cmd(struct drbd_cmd *cm, int argc, char **argv);
static int show_or_get_gi_cmd(struct drbd_cmd *cm, int argc, char **argv);
// sub commands for generic_get_cmd
static int print_notifications(struct drbd_cmd *, struct genl_info *, void *);
static int wait_for_family(struct drbd_cmd *, struct genl_info *, void *);
static int remember_resource(struct drbd_cmd *, struct genl_info *, void *);
static int remember_device(struct drbd_cmd *, struct genl_info *, void *);
static int remember_connection(struct drbd_cmd *, struct genl_info *, void *);
static int remember_peer_device(struct drbd_cmd *, struct genl_info *, void *);
#define ADDRESS_STR_MAX 256
static char *address_str(char *buffer, void* address, int addr_len);
// convert functions for arguments
static int conv_block_dev(struct drbd_argument *ad, struct msg_buff *msg, struct drbd_genlmsghdr *dhdr, char* arg);
static int conv_md_idx(struct drbd_argument *ad, struct msg_buff *msg, struct drbd_genlmsghdr *dhdr, char* arg);
static int conv_u32(struct drbd_argument *, struct msg_buff *, struct drbd_genlmsghdr *, char *);
static int conv_addr(struct drbd_argument *ad, struct msg_buff *msg, struct drbd_genlmsghdr *dhdr, char* arg);
struct resources_list {
struct resources_list *next;
char *name;
struct nlattr *res_opts;
struct resource_info info;
struct resource_statistics statistics;
};
static struct resources_list *list_resources(void);
static struct resources_list *sort_resources(struct resources_list *);
static void free_resources(struct resources_list *);
struct devices_list {
struct devices_list *next;
unsigned minor;
struct drbd_cfg_context ctx;
struct nlattr *disk_conf_nl;
struct disk_conf disk_conf;
struct device_info info;
struct device_statistics statistics;
};
static struct devices_list *list_devices(char *);
static void free_devices(struct devices_list *);
struct connections_list {
struct connections_list *next;
struct drbd_cfg_context ctx;
struct nlattr *path_list;
struct nlattr *net_conf;
struct connection_info info;
struct connection_statistics statistics;
};
static struct connections_list *sort_connections(struct connections_list *);
static struct connections_list *list_connections(char *);
static void free_connections(struct connections_list *);
struct peer_devices_list {
struct peer_devices_list *next;
struct drbd_cfg_context ctx;
struct nlattr *peer_device_conf;
struct peer_device_info info;
struct peer_device_statistics statistics;
struct devices_list *device;
int timeout_ms; /* used only by wait_for_family() */
};
static struct peer_devices_list *list_peer_devices(char *);
static void free_peer_devices(struct peer_devices_list *);
struct option wait_cmds_options[] = {
{ "wfc-timeout", required_argument, 0, 't' },
{ "degr-wfc-timeout", required_argument, 0, 'd'},
{ "outdated-wfc-timeout", required_argument, 0, 'o'},
{ "wait-after-sb", optional_argument, 0, 'w'},
{ }
};
struct option events_cmd_options[] = {
{ "timestamps", no_argument, 0, 'T' },
{ "statistics", no_argument, 0, 's' },
{ "now", no_argument, 0, 'n' },
{ "poll", no_argument, 0, 'p' },
{ "color", optional_argument, 0, 'c' },
{ }
};
struct option show_cmd_options[] = {
{ "show-defaults", no_argument, 0, 'D' },
{ }
};
static struct option status_cmd_options[] = {
{ "verbose", no_argument, 0, 'v' },
{ "statistics", no_argument, 0, 's' },
{ "color", optional_argument, 0, 'c' },
{ "json", no_argument, 0, 'j' },
{ }
};
#define F_CONFIG_CMD generic_config_cmd
#define NO_PAYLOAD 0
#define F_NEW_EVENTS_CMD(scmd) DRBD_ADM_GET_INITIAL_STATE, NO_PAYLOAD, generic_get_cmd, \
.show_function = scmd
struct drbd_cmd commands[] = {
{"primary", CTX_RESOURCE, DRBD_ADM_PRIMARY, DRBD_NLA_SET_ROLE_PARMS,
F_CONFIG_CMD,
.ctx = &primary_cmd_ctx,
.summary = "Change the role of a node in a resource to primary." },
{"secondary", CTX_RESOURCE, DRBD_ADM_SECONDARY, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Change the role of a node in a resource to secondary." },
{"attach", CTX_MINOR, DRBD_ADM_ATTACH, DRBD_NLA_DISK_CONF,
F_CONFIG_CMD,
.drbd_args = (struct drbd_argument[]) {
{ "lower_dev", T_backing_dev, conv_block_dev },
{ "meta_data_dev", T_meta_dev, conv_block_dev },
{ "meta_data_index", T_meta_dev_idx, conv_md_idx },
{ } },
.ctx = &attach_cmd_ctx,
.summary = "Attach a lower-level device to an existing replicated device." },
{"disk-options", CTX_MINOR, DRBD_ADM_CHG_DISK_OPTS, DRBD_NLA_DISK_CONF,
F_CONFIG_CMD,
.set_defaults = true,
.ctx = &disk_options_ctx,
.summary = "Change the disk options of an attached lower-level device." },
{"detach", CTX_MINOR, DRBD_ADM_DETACH, DRBD_NLA_DETACH_PARMS, F_CONFIG_CMD,
.ctx = &detach_cmd_ctx,
.summary = "Detach the lower-level device of a replicated device." },
{"connect", CTX_PEER_NODE,
DRBD_ADM_CONNECT, DRBD_NLA_CONNECT_PARMS,
F_CONFIG_CMD,
.ctx = &connect_cmd_ctx,
.summary = "Attempt to (re)establish a replication link to a peer host." },
{"new-peer", CTX_PEER_NODE,
DRBD_ADM_NEW_PEER, DRBD_NLA_NET_CONF,
F_CONFIG_CMD,
.ctx = &new_peer_cmd_ctx,
.summary = "Make a peer host known to a resource." },
{"del-peer", CTX_PEER_NODE,
DRBD_ADM_DEL_PEER, DRBD_NLA_DISCONNECT_PARMS,
F_CONFIG_CMD,
.ctx = &disconnect_cmd_ctx,
.summary = "Remove a connection to a peer host." },
{"new-path", CTX_PEER_NODE,
DRBD_ADM_NEW_PATH, DRBD_NLA_PATH_PARMS,
F_CONFIG_CMD,
.drbd_args = (struct drbd_argument[]) {
{ "local-addr", T_my_addr, conv_addr },
{ "remote-addr", T_peer_addr, conv_addr },
{ } },
.ctx = &path_cmd_ctx,
.summary = "Add a path (endpoint address pair) where a peer host should be reachable." },
{"del-path", CTX_PEER_NODE,
DRBD_ADM_DEL_PATH, DRBD_NLA_PATH_PARMS,
F_CONFIG_CMD,
.drbd_args = (struct drbd_argument[]) {
{ "local-addr", T_my_addr, conv_addr },
{ "remote-addr", T_peer_addr, conv_addr },
{ } },
.ctx = &path_cmd_ctx,
.summary = "Remove a path (endpoint address pair) from a connection to a peer host." },
{"net-options", CTX_PEER_NODE, DRBD_ADM_CHG_NET_OPTS, DRBD_NLA_NET_CONF,
F_CONFIG_CMD,
.set_defaults = true,
.ctx = &net_options_ctx,
.summary = "Change the network options of a connection." },
{"disconnect", CTX_PEER_NODE, DRBD_ADM_DISCONNECT, DRBD_NLA_DISCONNECT_PARMS,
F_CONFIG_CMD,
.ctx = &disconnect_cmd_ctx,
.summary = "Unconnect from a peer host." },
{"resize", CTX_MINOR, DRBD_ADM_RESIZE, DRBD_NLA_RESIZE_PARMS,
F_CONFIG_CMD,
.ctx = &resize_cmd_ctx,
.summary = "Reexamine the lower-level device sizes to resize a replicated device." },
{"resource-options", CTX_RESOURCE, DRBD_ADM_RESOURCE_OPTS, DRBD_NLA_RESOURCE_OPTS,
F_CONFIG_CMD,
.set_defaults = true,
.ctx = &resource_options_ctx,
.summary = "Change the resource options of an existing resource." },
{"peer-device-options", CTX_PEER_DEVICE, DRBD_ADM_CHG_PEER_DEVICE_OPTS,
DRBD_NLA_PEER_DEVICE_OPTS, F_CONFIG_CMD,
.set_defaults = true,
.ctx = &peer_device_options_ctx,
.summary = "Change peer-device options." },
{"new-current-uuid", CTX_MINOR, DRBD_ADM_NEW_C_UUID, DRBD_NLA_NEW_C_UUID_PARMS,
F_CONFIG_CMD,
.ctx = &new_current_uuid_cmd_ctx,
.summary = "Generate a new current UUID." },
{"invalidate", CTX_MINOR, DRBD_ADM_INVALIDATE, DRBD_NLA_INVALIDATE_PARMS, F_CONFIG_CMD,
.ctx = &invalidate_ctx,
.summary = "Replace the local data of a volume with that of a peer." },
{"invalidate-remote", CTX_PEER_DEVICE, DRBD_ADM_INVAL_PEER, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Replace a peer's data of a volume with the local data." },
{"pause-sync", CTX_PEER_DEVICE, DRBD_ADM_PAUSE_SYNC, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Stop resynchronizing between a local and a peer device." },
{"resume-sync", CTX_PEER_DEVICE, DRBD_ADM_RESUME_SYNC, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Allow resynchronization to resume on a replicated device." },
{"suspend-io", CTX_MINOR, DRBD_ADM_SUSPEND_IO, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Suspend I/O on a replicated device." },
{"resume-io", CTX_MINOR, DRBD_ADM_RESUME_IO, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Resume I/O on a replicated device." },
{"outdate", CTX_MINOR, DRBD_ADM_OUTDATE, NO_PAYLOAD, F_CONFIG_CMD,
.summary = "Mark the data on a lower-level device as outdated." },
{"verify", CTX_PEER_DEVICE, DRBD_ADM_START_OV, DRBD_NLA_START_OV_PARMS, F_CONFIG_CMD,
.ctx = &verify_cmd_ctx,
.summary = "Verify the data on a lower-level device against a peer device." },
{"down", CTX_RESOURCE | CTX_ALL, DRBD_ADM_DOWN, NO_PAYLOAD, down_cmd,
.missing_ok = true,
.warn_on_missing = true,
.summary = "Take a resource down." },
{"role", CTX_RESOURCE, 0, NO_PAYLOAD, role_cmd,
.lockless = true,
.summary = "Show the current role of a resource." },
{"cstate", CTX_PEER_NODE, 0, NO_PAYLOAD, cstate_cmd,
.lockless = true,
.summary = "Show the current state of a connection." },
{"dstate", CTX_MINOR, 0, NO_PAYLOAD, dstate_cmd,
.lockless = true,
.summary = "Show the current disk state of a lower-level device." },
{"show-gi", CTX_PEER_DEVICE, 0, NO_PAYLOAD, show_or_get_gi_cmd,
.lockless = true,
.summary = "Show the data generation identifiers for a device on a particular connection, with explanations." },
{"get-gi", CTX_PEER_DEVICE, 0, NO_PAYLOAD, show_or_get_gi_cmd,
.lockless = true,
.summary = "Show the data generation identifiers for a device on a particular connection." },
{"show", CTX_RESOURCE | CTX_ALL, 0, 0, show_cmd,
.options = show_cmd_options,
.lockless = true,
.summary = "Show the current configuration of a resource, or of all resources." },
{"status", CTX_RESOURCE | CTX_ALL, 0, 0, status_cmd,
.options = status_cmd_options,
.lockless = true,
.summary = "Show the state of a resource, or of all resources." },
{"check-resize", CTX_MINOR, 0, NO_PAYLOAD, check_resize_cmd,
.lockless = true,
.summary = "Remember the current size of a lower-level device." },
{"events2", CTX_RESOURCE | CTX_ALL, F_NEW_EVENTS_CMD(print_notifications),
.options = events_cmd_options,
.missing_ok = true,
.continuous_poll = true,
.lockless = true,
.summary = "Show the current state and all state changes of a resource, or of all resources." },
{"wait-sync-volume", CTX_PEER_DEVICE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until resync finished on a volume." },
{"wait-sync-connection", CTX_PEER_NODE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until resync finished on all volumes of a connection." },
{"wait-sync-resource", CTX_RESOURCE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until resync finished on all volumes." },
{"wait-connect-volume", CTX_PEER_DEVICE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until a device on a peer is visible." },
{"wait-connect-connection", CTX_PEER_NODE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until all peer volumes of connection are visible." },
{"wait-connect-resource", CTX_RESOURCE, F_NEW_EVENTS_CMD(wait_for_family),
.options = wait_cmds_options,
.continuous_poll = true,
.lockless = true,
.summary = "Wait until all connections are establised." },
{"new-resource", CTX_RESOURCE, DRBD_ADM_NEW_RESOURCE, DRBD_NLA_RESOURCE_OPTS, F_CONFIG_CMD,
.drbd_args = (struct drbd_argument[]) {
{ "node_id", T_node_id, conv_u32 },
{ } },
.ctx = &resource_options_ctx,
.summary = "Create a new resource." },
{"new-minor", CTX_RESOURCE | CTX_MINOR | CTX_VOLUME | CTX_MULTIPLE_ARGUMENTS,
DRBD_ADM_NEW_MINOR, DRBD_NLA_DEVICE_CONF,
F_CONFIG_CMD,
.ctx = &device_options_ctx,
.summary = "Create a new replicated device within a resource." },
{"del-minor", CTX_MINOR, DRBD_ADM_DEL_MINOR, NO_PAYLOAD, del_minor_cmd,
.summary = "Remove a replicated device." },
{"del-resource", CTX_RESOURCE, DRBD_ADM_DEL_RESOURCE, NO_PAYLOAD, del_resource_cmd,
.summary = "Remove a resource." },
{"forget-peer", CTX_RESOURCE, DRBD_ADM_FORGET_PEER, DRBD_NLA_FORGET_PEER_PARMS, F_CONFIG_CMD,
.drbd_args = (struct drbd_argument[]) {
{ "peer_node_id", T_forget_peer_node_id, conv_u32 },
{ } },
.summary = "Completely remove any reference to a unconnected peer from meta-data." },
};
bool show_defaults;
bool wait_after_split_brain;
#define OTHER_ERROR 900
#define EM(C) [ C - ERR_CODE_BASE ]
/* The EM(123) are used for old error messages. */
static const char *error_messages[] = {
EM(NO_ERROR) = "No further Information available.",
EM(ERR_LOCAL_ADDR) = "Local address(port) already in use.",
EM(ERR_PEER_ADDR) = "Remote address(port) already in use.",
EM(ERR_OPEN_DISK) = "Can not open backing device.",
EM(ERR_OPEN_MD_DISK) = "Can not open meta device.",
EM(106) = "Lower device already in use.",
EM(ERR_DISK_NOT_BDEV) = "Lower device is not a block device.",
EM(ERR_MD_NOT_BDEV) = "Meta device is not a block device.",
EM(109) = "Open of lower device failed.",
EM(110) = "Open of meta device failed.",
EM(ERR_DISK_TOO_SMALL) = "Low.dev. smaller than requested DRBD-dev. size.",
EM(ERR_MD_DISK_TOO_SMALL) = "Meta device too small.",
EM(113) = "You have to use the disk command first.",
EM(ERR_BDCLAIM_DISK) = "Lower device is already claimed. This usually means it is mounted.",
EM(ERR_BDCLAIM_MD_DISK) = "Meta device is already claimed. This usually means it is mounted.",
EM(ERR_MD_IDX_INVALID) = "Lower device / meta device / index combination invalid.",
EM(117) = "Currently we only support devices up to 3.998TB.\n"
"(up to 2TB in case you do not have CONFIG_LBD set)\n"
"Contact office@linbit.com, if you need more.",
EM(ERR_IO_MD_DISK) = "IO error(s) occurred during initial access to meta-data.\n",
EM(ERR_MD_UNCLEAN) = "Unclean meta-data found.\nYou need to 'drbdadm apply-al res'\n",
EM(ERR_MD_INVALID) = "No valid meta-data signature found.\n\n"
"\t==> Use 'drbdadm create-md res' to initialize meta-data area. <==\n",
EM(ERR_AUTH_ALG) = "The 'cram-hmac-alg' you specified is not known in "
"the kernel. (Maybe you need to modprobe it, or modprobe hmac?)",
EM(ERR_AUTH_ALG_ND) = "The 'cram-hmac-alg' you specified is not a digest.",
EM(ERR_NOMEM) = "kmalloc() failed. Out of memory?",
EM(ERR_DISCARD_IMPOSSIBLE) = "--discard-my-data not allowed when primary.",
EM(ERR_DISK_CONFIGURED) = "Device is attached to a disk (use detach first)",
EM(ERR_NET_CONFIGURED) = "Device has a net-config (use disconnect first)",
EM(ERR_MANDATORY_TAG) = "UnknownMandatoryTag",
EM(ERR_MINOR_INVALID) = "Device minor not allocated",
EM(128) = "Resulting device state would be invalid",
EM(ERR_INTR) = "Interrupted by Signal",
EM(ERR_RESIZE_RESYNC) = "Resize not allowed during resync.",
EM(ERR_NO_PRIMARY) = "Need one Primary node to resize.",
EM(ERR_RESYNC_AFTER) = "The resync-after minor number is invalid",
EM(ERR_RESYNC_AFTER_CYCLE) = "This would cause a resync-after dependency cycle",
EM(ERR_PAUSE_IS_SET) = "Sync-pause flag is already set",
EM(ERR_PAUSE_IS_CLEAR) = "Sync-pause flag is already cleared",
EM(136) = "Disk state is lower than outdated",
EM(ERR_PACKET_NR) = "Kernel does not know how to handle your request.\n"
"Maybe API_VERSION mismatch?",
EM(ERR_NO_DISK) = "Device does not have a disk-config",
EM(ERR_NOT_PROTO_C) = "Protocol C required",
EM(ERR_NOMEM_BITMAP) = "vmalloc() failed. Out of memory?",
EM(ERR_INTEGRITY_ALG) = "The 'data-integrity-alg' you specified is not known in "
"the kernel. (Maybe you need to modprobe it, or modprobe hmac?)",
EM(ERR_INTEGRITY_ALG_ND) = "The 'data-integrity-alg' you specified is not a digest.",
EM(ERR_CPU_MASK_PARSE) = "Invalid cpu-mask.",
EM(ERR_VERIFY_ALG) = "VERIFYAlgNotAvail",
EM(ERR_VERIFY_ALG_ND) = "VERIFYAlgNotDigest",
EM(ERR_VERIFY_RUNNING) = "Can not change verify-alg while online verify runs",
EM(ERR_DATA_NOT_CURRENT) = "Can only attach to the data we lost last (see kernel log).",
EM(ERR_CONNECTED) = "Need to be StandAlone",
EM(ERR_CSUMS_ALG) = "CSUMSAlgNotAvail",
EM(ERR_CSUMS_ALG_ND) = "CSUMSAlgNotDigest",
EM(ERR_CSUMS_RESYNC_RUNNING) = "Can not change csums-alg while resync is in progress",
EM(ERR_PERM) = "Permission denied. CAP_SYS_ADMIN necessary",
EM(ERR_NEED_APV_93) = "Protocol version 93 required to use --assume-clean",
EM(ERR_STONITH_AND_PROT_A) = "Fencing policy resource-and-stonith only with prot B or C allowed",
EM(ERR_CONG_NOT_PROTO_A) = "on-congestion policy pull-ahead only with prot A allowed",
EM(ERR_PIC_AFTER_DEP) = "Sync-pause flag is already cleared.\n"
"Note: Resync pause caused by a local resync-after dependency.",
EM(ERR_PIC_PEER_DEP) = "Sync-pause flag is already cleared.\n"
"Note: Resync pause caused by the peer node.",
EM(ERR_RES_NOT_KNOWN) = "Unknown resource",
EM(ERR_RES_IN_USE) = "Resource still in use (delete all minors first)",
EM(ERR_MINOR_CONFIGURED) = "Minor still configured (down it first)",
EM(ERR_MINOR_OR_VOLUME_EXISTS) = "Minor or volume exists already (delete it first)",
EM(ERR_INVALID_REQUEST) = "Invalid configuration request",
EM(ERR_NEED_APV_100) = "Prot version 100 required in order to change\n"
"these network options while connected",
EM(ERR_NEED_ALLOW_TWO_PRI) = "Can not clear allow_two_primaries as long as\n"
"there a primaries on both sides",
EM(ERR_MD_LAYOUT_CONNECTED) = "DRBD need to be connected for online MD layout change\n",
EM(ERR_MD_LAYOUT_TOO_BIG) = "Resulting AL area too big\n",
EM(ERR_MD_LAYOUT_TOO_SMALL) = "Resulting AL are too small\n",
EM(ERR_MD_LAYOUT_NO_FIT) = "Resulting AL does not fit into available meta data space\n",
EM(ERR_IMPLICIT_SHRINK) = "Implicit device shrinking not allowed. See kernel log.\n",
EM(ERR_INVALID_PEER_NODE_ID) = "Invalid peer-node-id\n",
EM(ERR_CREATE_TRANSPORT) = "Failed to create transport (drbd_transport_xxx module missing?)\n",
EM(ERR_LOCAL_AND_PEER_ADDR) = "Combination of local address(port) and remote address(port) already in use\n",
};
#define MAX_ERROR (sizeof(error_messages)/sizeof(*error_messages))
const char * error_to_string(int err_no)
{
const unsigned int idx = err_no - ERR_CODE_BASE;
if (idx >= MAX_ERROR) return "Unknown... maybe API_VERSION mismatch?";
return error_messages[idx];
}
#undef MAX_ERROR
char *cmdname = NULL; /* "drbdsetup" for reporting in usage etc. */
/*
* In CTX_MINOR, CTX_RESOURCE, CTX_ALL, objname and minor refer to the object
* the command operates on.
*/
char *objname;
unsigned minor = -1U;
struct drbd_cfg_context global_ctx;
enum cfg_ctx_key context;
int lock_fd;
struct genl_sock *drbd_sock = NULL;
struct genl_family drbd_genl_family = {
.name = "drbd",
.version = GENL_MAGIC_VERSION,
.hdrsize = GENL_MAGIC_FAMILY_HDRSZ,
};
#if 0
/* currently unused. */
static bool endpoints_equal(struct drbd_cfg_context *a, struct drbd_cfg_context *b)
{
return a->ctx_my_addr_len == b->ctx_my_addr_len &&
a->ctx_peer_addr_len == b->ctx_peer_addr_len &&
!memcmp(a->ctx_my_addr, b->ctx_my_addr, a->ctx_my_addr_len) &&
!memcmp(a->ctx_peer_addr, b->ctx_peer_addr, a->ctx_peer_addr_len);
}
#endif
static int conv_block_dev(struct drbd_argument *ad, struct msg_buff *msg,
struct drbd_genlmsghdr *dhdr, char* arg)
{
struct stat sb;
int device_fd;
if ((device_fd = open(arg,O_RDWR))==-1) {
PERROR("Can not open device '%s'", arg);
return OTHER_ERROR;
}
if (fstat(device_fd, &sb)) {
PERROR("fstat(%s) failed", arg);
return OTHER_ERROR;
}
if(!S_ISBLK(sb.st_mode)) {
fprintf(stderr, "%s is not a block device!\n", arg);
return OTHER_ERROR;
}
close(device_fd);
nla_put_string(msg, ad->nla_type, arg);
return NO_ERROR;
}
static int conv_md_idx(struct drbd_argument *ad, struct msg_buff *msg,
struct drbd_genlmsghdr *dhdr, char* arg)
{
int idx;
if(!strcmp(arg,"internal")) idx = DRBD_MD_INDEX_FLEX_INT;
else if(!strcmp(arg,"flexible")) idx = DRBD_MD_INDEX_FLEX_EXT;
else idx = m_strtoll(arg,1);
nla_put_u32(msg, ad->nla_type, idx);
return NO_ERROR;
}
static int conv_u32(struct drbd_argument *ad, struct msg_buff *msg,
struct drbd_genlmsghdr *dhdr, char* arg)
{
unsigned int i = m_strtoll(arg, 1);
nla_put_u32(msg, ad->nla_type, i);
return NO_ERROR;
}
static void resolv6(const char *name, struct sockaddr_in6 *addr)
{
struct addrinfo hints, *res, *tmp;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo(name, 0, &hints, &res);
if (err) {
fprintf(stderr, "getaddrinfo %s: %s\n", name, gai_strerror(err));
exit(20);
}
/* Yes, it is a list. We use only the first result. The loop is only
* there to document that we know it is a list */
for (tmp = res; tmp; tmp = tmp->ai_next) {
memcpy(addr, tmp->ai_addr, sizeof(*addr));
break;
}
freeaddrinfo(res);
if (0) { /* debug output */
char ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
fprintf(stderr, "%s -> %02x %04x %08x %s %08x\n",
name,
addr->sin6_family,
addr->sin6_port,
addr->sin6_flowinfo,
ip,
addr->sin6_scope_id);
}
}
static unsigned long resolv(const char* name)
{
unsigned long retval;
if((retval = inet_addr(name)) == INADDR_NONE ) {
struct hostent *he;
he = gethostbyname(name);
if (!he) {
fprintf(stderr, "can not resolve the hostname: gethostbyname(%s): %s\n",
name, hstrerror(h_errno));
exit(20);
}
retval = ((struct in_addr *)(he->h_addr_list[0]))->s_addr;
}
return retval;
}
static void split_ipv6_addr(char **address, int *port)
{
/* ipv6:[fe80::0234:5678:9abc:def1]:8000; */
char *b = strrchr(*address,']');
if (address[0][0] != '[' || b == NULL ||
(b[1] != ':' && b[1] != '\0')) {
fprintf(stderr, "unexpected ipv6 format: %s\n",
*address);
exit(20);
}
*b = 0;
*address += 1; /* skip '[' */
if (b[1] == ':')
*port = m_strtoll(b+2,1); /* b+2: "]:" */
else
*port = 7788; /* will we ever get rid of that default port? */
}
static void split_address(int *af, char** address, int* port)
{
static struct { char* text; int af; } afs[] = {
{ "ipv4:", AF_INET },
{ "ipv6:", AF_INET6 },
{ "sdp:", AF_INET_SDP },
{ "ssocks:", -1 },
};
unsigned int i;
char *b;
*af=AF_INET;
for (i=0; i<ARRAY_SIZE(afs); i++) {
if (!strncmp(*address, afs[i].text, strlen(afs[i].text))) {
*af = afs[i].af;
*address += strlen(afs[i].text);
break;
}
}
if (*af == AF_INET6 && address[0][0] == '[')
return split_ipv6_addr(address, port);
if (*af == -1)
*af = get_af_ssocks(1);
b=strrchr(*address,':');
if (b) {
*b = 0;
if (*af == AF_INET6) {
/* compatibility handling of ipv6 addresses,
* in the style expected before drbd 8.3.9.
* may go wrong without explicit port */
fprintf(stderr, "interpreting ipv6:%s:%s as ipv6:[%s]:%s\n",
*address, b+1, *address, b+1);
}
*port = m_strtoll(b+1,1);
} else
*port = 7788;
}
static int sockaddr_from_str(struct sockaddr_storage *storage, const char *str)
{
int af, port;
char *address = strdupa(str);
split_address(&af, &address, &port);
if (af == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)storage;
memset(sin6, 0, sizeof(*sin6));
resolv6(address, sin6);
sin6->sin6_port = htons(port);
/* sin6->sin6_len = sizeof(*sin6); */
return sizeof(*sin6);
} else {
/* AF_INET, AF_SDP, AF_SSOCKS,
* all use the IPv4 addressing scheme */
struct sockaddr_in *sin = (struct sockaddr_in *)storage;
memset(sin, 0, sizeof(*sin));
sin->sin_port = htons(port);
sin->sin_family = af;
sin->sin_addr.s_addr = resolv(address);
return sizeof(*sin);
}
return 0;
}
static int conv_addr(struct drbd_argument *ad, struct msg_buff *msg,
struct drbd_genlmsghdr *dhdr, char* arg)
{
struct sockaddr_storage x;
int addr_len;
if (strncmp(arg, "local:", 6) == 0)
arg += 6;
else if (strncmp(arg, "peer:", 5) == 0)
arg += 5;
addr_len = sockaddr_from_str(&x, arg);
if (addr_len == 0) {
fprintf(stderr, "does not look like an endpoint address '%s'", arg);
return OTHER_ERROR;
}
nla_put(msg, ad->nla_type, addr_len, &x);
return NO_ERROR;
}
/* It will only print the WARNING if the warn flag is set
with the _first_ call! */
#define PROC_NET_AF_SCI_FAMILY "/proc/net/af_sci/family"
#define PROC_NET_AF_SSOCKS_FAMILY "/proc/net/af_ssocks/family"
static int get_af_ssocks(int warn_and_use_default)
{
char buf[16];
int c, fd;
static int af = -1;
if (af > 0)
return af;
fd = open(PROC_NET_AF_SSOCKS_FAMILY, O_RDONLY);
if (fd < 0)
fd = open(PROC_NET_AF_SCI_FAMILY, O_RDONLY);
if (fd < 0) {
if (warn_and_use_default) {
fprintf(stderr, "open(" PROC_NET_AF_SSOCKS_FAMILY ") "
"failed: %m\n WARNING: assuming AF_SSOCKS = 27. "
"Socket creation may fail.\n");
af = 27;
}
return af;
}
c = read(fd, buf, sizeof(buf)-1);
if (c > 0) {
buf[c] = 0;
if (buf[c-1] == '\n')
buf[c-1] = 0;
af = m_strtoll(buf,1);
} else {
if (warn_and_use_default) {
fprintf(stderr, "read(" PROC_NET_AF_SSOCKS_FAMILY ") "
"failed: %m\n WARNING: assuming AF_SSOCKS = 27. "
"Socket creation may fail.\n");
af = 27;
}
}
close(fd);
return af;
}
static struct option *make_longoptions(struct drbd_cmd *cm)
{
static struct option buffer[47];
int i = 0;
int primary_force_index = -1;
int connect_tentative_index = -1;
if (cm->ctx) {
struct field_def *field;
/*
* Make sure to keep cm->ctx->fields first: we use the index
* returned by getopt_long() to access cm->ctx->fields.
*/
for (field = cm->ctx->fields; field->name; field++) {
assert(i < ARRAY_SIZE(buffer));
buffer[i].name = field->name;
buffer[i].has_arg = field->argument_is_optional ?
optional_argument : required_argument;
buffer[i].flag = NULL;
buffer[i].val = 0;
if (!strcmp(cm->cmd, "primary") && !strcmp(field->name, "force"))
primary_force_index = i;
if (!strcmp(cm->cmd, "connect") && !strcmp(field->name, "tentative"))
connect_tentative_index = i;
i++;
}
assert(field - cm->ctx->fields == i);
}
if (cm->options) {
struct option *option;
for (option = cm->options; option->name; option++) {
assert(i < ARRAY_SIZE(buffer));
buffer[i] = *option;
i++;
}
}
if (primary_force_index != -1) {
/*
* For backward compatibility, add --overwrite-data-of-peer as
* an alias to --force.
*/
assert(i < ARRAY_SIZE(buffer));
buffer[i] = buffer[primary_force_index];
buffer[i].name = "overwrite-data-of-peer";
buffer[i].val = 1000 + primary_force_index;
i++;
}
if (connect_tentative_index != -1) {
/*
* For backward compatibility, add --dry-run as an alias to
* --tentative.
*/
assert(i < ARRAY_SIZE(buffer));
buffer[i] = buffer[connect_tentative_index];
buffer[i].name = "dry-run";
buffer[i].val = 1000 + connect_tentative_index;
i++;
}
if (cm->set_defaults) {
assert(i < ARRAY_SIZE(buffer));
buffer[i].name = "set-defaults";
buffer[i].has_arg = 0;
buffer[i].flag = NULL;
buffer[i].val = '(';
i++;
}
assert(i < ARRAY_SIZE(buffer));
buffer[i].name = NULL;
buffer[i].has_arg = 0;
buffer[i].flag = NULL;
buffer[i].val = 0;
return buffer;
}
/* prepends global objname to output (if any) */
static int check_error(int err_no, char *desc)
{
int rv = 0;
if (err_no == NO_ERROR || err_no == SS_SUCCESS)
return 0;
if (err_no == OTHER_ERROR) {
if (desc)
fprintf(stderr,"%s: %s\n", objname, desc);
return 20;
}
if ( ( err_no >= AFTER_LAST_ERR_CODE || err_no <= ERR_CODE_BASE ) &&
( err_no > SS_CW_NO_NEED || err_no <= SS_AFTER_LAST_ERROR) ) {
fprintf(stderr,"%s: Error code %d unknown.\n"
"You should update the drbd userland tools.\n",
objname, err_no);
rv = 20;
} else {
if(err_no > ERR_CODE_BASE ) {
fprintf(stderr,"%s: Failure: (%d) %s\n",
objname, err_no, desc ?: error_to_string(err_no));
rv = 10;
} else if (err_no == SS_UNKNOWN_ERROR) {
fprintf(stderr,"%s: State change failed: (%d)"
"unknown error.\n", objname, err_no);
rv = 11;
} else if (err_no > SS_TWO_PRIMARIES) {
// Ignore SS_SUCCESS, SS_NOTHING_TO_DO, SS_CW_Success...
} else {
fprintf(stderr,"%s: State change failed: (%d) %s\n",
objname, err_no, drbd_set_st_err_str(err_no));
if (err_no == SS_NO_UP_TO_DATE_DISK) {
/* all available disks are inconsistent,
* or I am consistent, but cannot outdate the peer. */
rv = 17;
} else if (err_no == SS_LOWER_THAN_OUTDATED) {
/* was inconsistent anyways */
rv = 5;
} else if (err_no == SS_NO_LOCAL_DISK) {
/* Can not start resync, no local disks, try with drbdmeta */
rv = 16;
} else {
rv = 11;
}
}
}
if (global_attrs[DRBD_NLA_CFG_REPLY] &&
global_attrs[DRBD_NLA_CFG_REPLY]->nla_len) {
struct nlattr *nla;
int rem;
fprintf(stderr, "additional info from kernel:\n");
nla_for_each_nested(nla, global_attrs[DRBD_NLA_CFG_REPLY], rem) {
if (nla_type(nla) == __nla_type(T_info_text))
fprintf(stderr, "%s\n", (char*)nla_data(nla));
}
}
return rv;
}
static void warn_print_excess_args(int argc, char **argv, int i)
{
fprintf(stderr, "Excess arguments:");
for (; i < argc; i++)
fprintf(stderr, " %s", argv[i]);
printf("\n");
}
int drbd_tla_parse(struct nlmsghdr *nlh)
{
return nla_parse(global_attrs, ARRAY_SIZE(drbd_tla_nl_policy)-1,
nlmsg_attrdata(nlh, GENL_HDRLEN + drbd_genl_family.hdrsize),
nlmsg_attrlen(nlh, GENL_HDRLEN + drbd_genl_family.hdrsize),
drbd_tla_nl_policy);
}
#define ASSERT(exp) if (!(exp)) \
fprintf(stderr,"ASSERT( " #exp " ) in %s:%d\n", __FILE__,__LINE__);
static int _generic_config_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct drbd_argument *ad;
struct nlattr *nla;
struct option *options;
int c, i;
int rv;
char *desc = NULL; /* error description from kernel reply message */
struct drbd_genlmsghdr *dhdr;
struct msg_buff *smsg;
struct iovec iov;
struct nlmsghdr *nlh;
struct drbd_genlmsghdr *dh;
struct timespec retry_timeout = {
.tv_nsec = 62500000L, /* 1/16 second */
};
/* pre allocate request message and reply buffer */
iov.iov_len = DEFAULT_MSG_SIZE;
iov.iov_base = malloc(iov.iov_len);
smsg = msg_new(DEFAULT_MSG_SIZE);
if (!smsg || !iov.iov_base) {
desc = "could not allocate netlink messages";
rv = OTHER_ERROR;
goto error;
}
nlh = (struct nlmsghdr*)iov.iov_base;
dh = genlmsg_data(nlmsg_data(nlh));
dhdr = genlmsg_put(smsg, &drbd_genl_family, 0, cm->cmd_id);
dhdr->minor = -1;
dhdr->flags = 0;
if (context & CTX_MINOR)
dhdr->minor = minor;
if (context & ~CTX_MINOR) {
nla = nla_nest_start(smsg, DRBD_NLA_CFG_CONTEXT);
if (context & CTX_RESOURCE)
nla_put_string(smsg, T_ctx_resource_name, objname);
if (context & CTX_PEER_NODE_ID)
nla_put_u32(smsg, T_ctx_peer_node_id, global_ctx.ctx_peer_node_id);
if (context & CTX_VOLUME)
nla_put_u32(smsg, T_ctx_volume, global_ctx.ctx_volume);
nla_nest_end(smsg, nla);
}
nla = NULL;
options = make_longoptions(cm);
optind = 0; /* reset getopt_long() */
for (;;) {
int idx;
c = getopt_long(argc, argv, "(", options, &idx);
if (c == -1)
break;
if (c >= 1000) {
/* This is a field alias. */
idx = c - 1000;
c = 0;
}
if (c == 0) {
struct field_def *field = &cm->ctx->fields[idx];
assert (field->name == options[idx].name);
if (!nla) {
assert (cm->tla_id != NO_PAYLOAD);
nla = nla_nest_start(smsg, cm->tla_id);
}
if (!field->ops->put(cm->ctx, field, smsg, optarg)) {
fprintf(stderr, "Option --%s: invalid "
"argument '%s'\n",
field->name, optarg);
rv = OTHER_ERROR;
goto error;
}
} else if (c == '(')
dhdr->flags |= DRBD_GENL_F_SET_DEFAULTS;
else {
rv = OTHER_ERROR;
goto error;
}
}
for (i = optind, ad = cm->drbd_args; ad && ad->name; i++) {
if (argc < i + 1) {
fprintf(stderr, "Missing argument '%s'\n", ad->name);
print_command_usage(cm, FULL);
rv = OTHER_ERROR;
goto error;
}
if (!nla) {
assert (cm->tla_id != NO_PAYLOAD);
nla = nla_nest_start(smsg, cm->tla_id);
}
rv = ad->convert_function(ad, smsg, dhdr, argv[i]);
if (rv != NO_ERROR)
goto error;
ad++;
}
/* dhdr->minor may have been set by one of the convert functions. */
minor = dhdr->minor;
if (nla)
nla_nest_end(smsg, nla);
/* argc should be cmd + n options + n args;
* if it is more, we did not understand some */
if (i < argc) {
warn_print_excess_args(argc, argv, i);
rv = OTHER_ERROR;
goto error;
}
if (strcmp(cm->cmd, "new-minor") == 0) {
/* HACK */
/* hack around "sysfs: cannot create duplicate filename '/devices/virtual/bdi/147:0'"
* and subsequent NULL deref in kernel below add_disk(). */
struct stat sb;
char buf[PATH_MAX];
int i;
int c = 0;
for (i = 0; i <= 2; i++) {
if (i == 0) c = snprintf(buf, PATH_MAX, "/sys/devices/virtual/block/drbd%u", minor);
if (i == 1) c = snprintf(buf, PATH_MAX, "/sys/devices/virtual/bdi/147:%u", minor);
if (i == 2) c = snprintf(buf, PATH_MAX, "/sys/block/drbd%u", minor);
if (c < PATH_MAX) {
if (lstat(buf, &sb) == 0) {
syslog(LOG_ERR, "new-minor %s %u %u: sysfs node '%s' (already? still?) exists\n",
objname, minor, global_ctx.ctx_volume, buf);
fprintf(stderr, "new-minor %s %u %u: sysfs node '%s' (already? still?) exists\n",
objname, minor, global_ctx.ctx_volume, buf);
rv = ERR_MINOR_OR_VOLUME_EXISTS;
desc = NULL;
goto error;
}
}
}
}
for(;;) {
if (genl_send(drbd_sock, smsg)) {
desc = "error sending config command";
rv = OTHER_ERROR;
goto error;
}
do {
int received;
/* reduce timeout! limit retries */
received = genl_recv_msgs(drbd_sock, &iov, &desc, 120000);
if (received <= 0) {
if (received == -E_RCV_ERROR_REPLY && !errno)
continue;
if (!desc)
desc = "error receiving config reply";
rv = OTHER_ERROR;
goto error;
}
} while (false);
ASSERT(dh->minor == minor);
rv = dh->ret_code;
if (rv != SS_IN_TRANSIENT_STATE)
break;
nanosleep(&retry_timeout, NULL);
/* Double the timeout, up to 10 seconds. */
if (retry_timeout.tv_sec < 10) {
retry_timeout.tv_sec *= 2;
retry_timeout.tv_nsec *= 2;
if (retry_timeout.tv_nsec > 1000000000L) {
retry_timeout.tv_sec++;
retry_timeout.tv_nsec -= 1000000000L;
}
}
}
if (rv == ERR_RES_NOT_KNOWN) {
if (cm->warn_on_missing && isatty(STDERR_FILENO))
fprintf(stderr, "Resource unknown\n");
if (cm->missing_ok)
rv = NO_ERROR;
}
drbd_tla_parse(nlh);
error:
msg_free(smsg);
rv = check_error(rv, desc);
free(iov.iov_base);
return rv;
}
static int generic_config_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
return _generic_config_cmd(cm, argc, argv);
}
static int del_minor_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
int rv;
rv = generic_config_cmd(cm, argc, argv);
if (!rv)
unregister_minor(minor);
return rv;
}
static int del_resource_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
int rv;
rv = generic_config_cmd(cm, argc, argv);
if (!rv)
unregister_resource(objname);
return rv;
}
static struct drbd_cmd *find_cmd_by_name(const char *name)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (!strcmp(name, commands[i].cmd)) {
return commands + i;
}
}
return NULL;
}
static void print_options(struct nlattr *attr, struct context_def *ctx, const char *sect_name)
{
struct field_def *field;
int opened = 0;
if (!attr)
return;
if (drbd_nla_parse_nested(nested_attr_tb, ctx->nla_policy_size - 1,
attr, ctx->nla_policy)) {
fprintf(stderr, "nla_policy violation for %s payload!\n", sect_name);
/* still, print those that validated ok */
}
for (field = ctx->fields; field->name; field++) {
struct nlattr *nlattr;
const char *str;
bool is_default;
nlattr = ntb(field->nla_type);
if (!nlattr)
continue;
str = field->ops->get(ctx, field, nlattr);
is_default = field->ops->is_default(field, str);
if (is_default && !show_defaults)
continue;
if (!opened) {
opened=1;
printI("%s {\n",sect_name);
++indent;
}
if (field->needs_double_quoting)
str = double_quote_string(str);
printI("%-16s\t%s;",field->name, str);
if (field->unit || is_default) {
printf(" # ");
if (field->unit)
printf("%s", field->unit);
if (field->unit && is_default)
printf(", ");
if (is_default)
printf("default");
}
printf("\n");
}
if(opened) {
--indent;
printI("}\n");
}
}
struct choose_timeout_ctx {
struct drbd_cfg_context ctx;
struct msg_buff *smsg;
struct iovec *iov;
int timeout;
int wfc_timeout;
int degr_wfc_timeout;
int outdated_wfc_timeout;
};
int choose_timeout(struct choose_timeout_ctx *ctx)
{
char *desc = NULL;
struct drbd_genlmsghdr *dhdr;
struct nlattr *nla;
int rr;
if (0 < ctx->wfc_timeout &&
(ctx->wfc_timeout < ctx->degr_wfc_timeout || ctx->degr_wfc_timeout == 0)) {
ctx->degr_wfc_timeout = ctx->wfc_timeout;
fprintf(stderr, "degr-wfc-timeout has to be shorter than wfc-timeout\n"
"degr-wfc-timeout implicitly set to wfc-timeout (%ds)\n",
ctx->degr_wfc_timeout);
}
if (0 < ctx->degr_wfc_timeout &&
(ctx->degr_wfc_timeout < ctx->outdated_wfc_timeout || ctx->outdated_wfc_timeout == 0)) {
ctx->outdated_wfc_timeout = ctx->wfc_timeout;
fprintf(stderr, "outdated-wfc-timeout has to be shorter than degr-wfc-timeout\n"
"outdated-wfc-timeout implicitly set to degr-wfc-timeout (%ds)\n",
ctx->degr_wfc_timeout);
}
dhdr = genlmsg_put(ctx->smsg, &drbd_genl_family, 0, DRBD_ADM_GET_TIMEOUT_TYPE);
dhdr->minor = -1;
dhdr->flags = 0;
nla = nla_nest_start(ctx->smsg, DRBD_NLA_CFG_CONTEXT);
nla_put_string(ctx->smsg, T_ctx_resource_name, ctx->ctx.ctx_resource_name);
nla_put_u32(ctx->smsg, T_ctx_peer_node_id, ctx->ctx.ctx_peer_node_id);
nla_put_u32(ctx->smsg, T_ctx_volume, ctx->ctx.ctx_volume);
nla_nest_end(ctx->smsg, nla);
if (genl_send(drbd_sock, ctx->smsg)) {
desc = "error sending config command";
goto error;
}
rr = genl_recv_msgs(drbd_sock, ctx->iov, &desc, 120000);
if (rr > 0) {
struct nlmsghdr *nlh = (struct nlmsghdr*)ctx->iov->iov_base;
struct genl_info info = {
.seq = nlh->nlmsg_seq,
.nlhdr = nlh,
.genlhdr = nlmsg_data(nlh),
.userhdr = genlmsg_data(nlmsg_data(nlh)),
.attrs = global_attrs,
};
struct drbd_genlmsghdr *dh = info.userhdr;
struct timeout_parms parms;
rr = dh->ret_code;
if (rr == ERR_MINOR_INVALID) {
desc = "minor not available";
goto error;
}
if (rr != NO_ERROR)
goto error;
if (drbd_tla_parse(nlh)
|| timeout_parms_from_attrs(&parms, &info)) {
desc = "reply did not validate - "
"do you need to upgrade your userland tools?";
goto error;
}
rr = parms.timeout_type;
ctx->timeout =
(rr == UT_DEGRADED) ? ctx->degr_wfc_timeout :
(rr == UT_PEER_OUTDATED) ? ctx->outdated_wfc_timeout :
ctx->wfc_timeout;
return 0;
}
error:
if (!desc)
desc = "error receiving netlink reply";
fprintf(stderr, "error determining which timeout to use: %s\n",
desc);
return 20;
}
#include <sys/utsname.h>
static bool kernel_older_than(int version, int patchlevel, int sublevel)
{
struct utsname utsname;
char *rel;
int l;
if (uname(&utsname) != 0)
return false;
rel = utsname.release;
l = strtol(rel, &rel, 10);
if (l > version)
return false;
else if (l < version || *rel == 0)
return true;
l = strtol(rel + 1, &rel, 10);
if (l > patchlevel)
return false;
else if (l < patchlevel || *rel == 0)
return true;
l = strtol(rel + 1, &rel, 10);
if (l >= sublevel)
return false;
return true;
}
static int shortest_timeout(struct peer_devices_list *peer_devices)
{
struct peer_devices_list *peer_device;
int timeout = -1;
/* There is no point waiting for peers I do not even know about. */
if (peer_devices == NULL)
return 1;
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (peer_device->timeout_ms > 0 &&
(peer_device->timeout_ms < timeout || timeout == -1))
timeout = peer_device->timeout_ms;
}
return timeout;
}
static bool update_timeouts(struct peer_devices_list *peer_devices, int elapsed)
{
struct peer_devices_list *peer_device;
bool all_expired = true;
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (peer_device->timeout_ms != -1) {
peer_device->timeout_ms -= elapsed;
if (peer_device->timeout_ms < 0)
peer_device->timeout_ms = 0;
}
if (peer_device->timeout_ms != 0)
all_expired = false;
}
return all_expired;
}
static bool parse_color_argument(void)
{
if (!optarg || !strcmp(optarg, "always"))
opt_color = ALWAYS_COLOR;
else if (!strcmp(optarg, "never"))
opt_color = NEVER_COLOR;
else if (!strcmp(optarg, "auto"))
opt_color = AUTO_COLOR;
else
return 0;
return 1;
}
static bool opt_now;
static bool opt_poll;
static bool opt_verbose;
static bool opt_statistics;
static bool opt_timestamps;
static int generic_get(struct drbd_cmd *cm, int timeout_arg, void *u_ptr)
{
char *desc = NULL;
struct drbd_genlmsghdr *dhdr;
struct msg_buff *smsg;
struct iovec iov;
int timeout_ms, flags;
int rv = NO_ERROR;
int err = 0;
/* pre allocate request message and reply buffer */
iov.iov_len = DEFAULT_MSG_SIZE;
iov.iov_base = malloc(iov.iov_len);
smsg = msg_new(DEFAULT_MSG_SIZE);
if (!smsg || !iov.iov_base) {
desc = "could not allocate netlink messages";
rv = OTHER_ERROR;
goto out;
}
if (cm->continuous_poll) {
/* also always (try to) listen to nlctrl notify,
* so we have a chance to notice rmmod. */
int id = GENL_ID_CTRL;
setsockopt(drbd_sock->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&id, sizeof(id));
if (genl_join_mc_group(drbd_sock, "events") &&
!kernel_older_than(2, 6, 23)) {
desc = "unable to join drbd events multicast group";
rv = OTHER_ERROR;
goto out2;
}
}
flags = 0;
if (minor == -1U)
flags |= NLM_F_DUMP;
dhdr = genlmsg_put(smsg, &drbd_genl_family, flags, cm->cmd_id);
dhdr->minor = minor;
dhdr->flags = 0;
if (minor == -1U && strcmp(objname, "all")) {
/* Restrict the dump to a single resource. */
struct nlattr *nla;
nla = nla_nest_start(smsg, DRBD_NLA_CFG_CONTEXT);
nla_put_string(smsg, T_ctx_resource_name, objname);
nla_nest_end(smsg, nla);
}
if (genl_send(drbd_sock, smsg)) {
desc = "error sending config command";
rv = OTHER_ERROR;
goto out2;
}
/* disable sequence number check in genl_recv_msgs */
drbd_sock->s_seq_expect = 0;
for (;;) {
int received, rem, ret;
struct nlmsghdr *nlh = (struct nlmsghdr *)iov.iov_base;
struct timeval before;
struct pollfd pollfds[2] = {
[0] = {
.fd = 1,
.events = POLLHUP,
},
[1] = {
.fd = drbd_sock->s_fd,
.events = POLLIN,
},
};
gettimeofday(&before, NULL);
timeout_ms =
timeout_arg == MULTIPLE_TIMEOUTS ? shortest_timeout(u_ptr) : timeout_arg;
ret = poll(pollfds, 2, timeout_ms);
if (ret == 0) {
err = 5;
goto out2;
}
if (pollfds[0].revents == POLLERR || pollfds[0].revents == POLLHUP)
goto out2;
received = genl_recv_msgs(drbd_sock, &iov, &desc, -1);
if (received < 0) {
switch(received) {
case E_RCV_TIMEDOUT:
err = 5;
goto out2;
case -E_RCV_FAILED:
err = 20;
goto out2;
case -E_RCV_NO_SOURCE_ADDR:
continue; /* ignore invalid message */
case -E_RCV_SEQ_MISMATCH:
/* we disabled it, so it should not happen */
err = 20;
goto out2;
case -E_RCV_MSG_TRUNC:
continue;
case -E_RCV_UNEXPECTED_TYPE:
continue;
case -E_RCV_NLMSG_DONE:
if (cm->continuous_poll)
continue;
err = cm->show_function(cm, NULL, u_ptr);
if (err)
goto out2;
err = -*(int*)nlmsg_data(nlh);
if (err &&
(err != ENODEV || !cm->missing_ok)) {
fprintf(stderr, "received netlink error reply: %s\n",
strerror(err));
err = 20;
}
goto out2;
case -E_RCV_ERROR_REPLY:
if (!errno) /* positive ACK message */
continue;
if (!desc)
desc = strerror(errno);
fprintf(stderr, "received netlink error reply: %s\n",
desc);
err = 20;
goto out2;
default:
if (!desc)
desc = "error receiving config reply";
err = 20;
goto out2;
}
}
if (timeout_ms != -1) {
struct timeval after;
int elapsed_ms;
bool exit;
gettimeofday(&after, NULL);
elapsed_ms =
(after.tv_sec - before.tv_sec) * 1000 +
(after.tv_usec - before.tv_usec) / 1000;
if (timeout_arg == MULTIPLE_TIMEOUTS) {
exit = update_timeouts(u_ptr, elapsed_ms);
} else {
timeout_ms -= elapsed_ms;
exit = timeout_ms <= 0;
}
if (exit) {
err = 5;
goto out2;
}
}
/* There may be multiple messages in one datagram (for dump replies). */
nlmsg_for_each_msg(nlh, nlh, received, rem) {
struct drbd_genlmsghdr *dh = genlmsg_data(nlmsg_data(nlh));
struct genl_info info = (struct genl_info){
.seq = nlh->nlmsg_seq,
.nlhdr = nlh,
.genlhdr = nlmsg_data(nlh),
.userhdr = genlmsg_data(nlmsg_data(nlh)),
.attrs = global_attrs,
};
dbg(3, "received type:%x\n", nlh->nlmsg_type);
if (nlh->nlmsg_type < NLMSG_MIN_TYPE) {
/* Ignore netlink control messages. */
continue;
}
if (nlh->nlmsg_type == GENL_ID_CTRL) {
#ifdef HAVE_CTRL_CMD_DELMCAST_GRP
dbg(3, "received cmd:%x\n", info.genlhdr->cmd);
if (info.genlhdr->cmd == CTRL_CMD_DELMCAST_GRP) {
struct nlattr *nla =
nlmsg_find_attr(nlh, GENL_HDRLEN, CTRL_ATTR_FAMILY_ID);
if (nla && nla_get_u16(nla) == drbd_genl_family.id) {
/* FIXME: We could wait for the
multicast group to be recreated ... */
goto out2;
}
}
#endif
/* Ignore other generic netlink control messages. */
continue;
}
if (nlh->nlmsg_type != drbd_genl_family.id) {
/* Ignore messages for all other netlink families. */
continue;
}
/* parse early, otherwise drbd_cfg_context_from_attrs
* can not work */
if (drbd_tla_parse(nlh)) {
/* FIXME
* should continuous_poll continue?
*/
desc = "reply did not validate - "
"do you need to upgrade your userland tools?";
rv = OTHER_ERROR;
goto out2;
}
if (cm->continuous_poll) {
struct drbd_cfg_context ctx;
/*
* We will receive all events and have to
* filter for what we want ourself.
*/
/* FIXME
* Do we want to ignore broadcasts until the
* initial get/dump requests is done? */
if (!drbd_cfg_context_from_attrs(&ctx, &info)) {
switch ((int)cm->ctx_key) {
case CTX_MINOR:
/* Assert that, for an unicast reply,
* reply minor matches request minor.
* "unsolicited" kernel broadcasts are "pid=0" (netlink "port id")
* (and expected to be genlmsghdr.cmd == DRBD_EVENT) */
if (minor != dh->minor) {
if (info.nlhdr->nlmsg_pid != 0)
dbg(1, "received netlink packet for minor %u, while expecting %u\n",
dh->minor, minor);
continue;
}
break;
case CTX_PEER_DEVICE:
if (ctx.ctx_volume != global_ctx.ctx_volume)
continue;
/* also needs to match the connection, of course */
case CTX_PEER_NODE:
if (ctx.ctx_peer_node_id != global_ctx.ctx_peer_node_id)
continue;
/* also needs to match the resource, of course */
case CTX_RESOURCE:
case CTX_RESOURCE | CTX_ALL:
if (!strcmp(objname, "all"))
break;
if (strcmp(objname, ctx.ctx_resource_name))
continue;
break;
default:
fprintf(stderr, "DRECK: %x\n", cm->ctx_key);
assert(0);
}
}
}
rv = dh->ret_code;
if (rv == ERR_MINOR_INVALID && cm->missing_ok)
rv = NO_ERROR;
if (rv != NO_ERROR)
goto out2;
err = cm->show_function(cm, &info, u_ptr);
if (err) {
if (err < 0)
err = 0;
goto out2;
}
}
if (!cm->continuous_poll && !(flags & NLM_F_DUMP)) {
/* There will be no more reply packets. */
err = cm->show_function(cm, NULL, u_ptr);
goto out2;
}
}
out2:
msg_free(smsg);
out:
if (!err)
err = check_error(rv, desc);
free(iov.iov_base);
return err;
}
static int generic_get_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
static struct option no_options[] = { { } };
struct choose_timeout_ctx timeo_ctx = {
.wfc_timeout = DRBD_WFC_TIMEOUT_DEF,
.degr_wfc_timeout = DRBD_DEGR_WFC_TIMEOUT_DEF,
.outdated_wfc_timeout = DRBD_OUTDATED_WFC_TIMEOUT_DEF,
};
int c, timeout_ms, err = NO_ERROR;
struct peer_devices_list *peer_devices = NULL;
struct option *options = cm->options ? cm->options : no_options;
const char *opts = make_optstring(options);
optind = 0; /* reset getopt_long() */
for(;;) {
c = getopt_long(argc, argv, opts, options, 0);
if (c == -1)
break;
switch(c) {
default:
case '?':
return 20;
case 't':
timeo_ctx.wfc_timeout = m_strtoll(optarg, 1);
if(DRBD_WFC_TIMEOUT_MIN > timeo_ctx.wfc_timeout ||
timeo_ctx.wfc_timeout > DRBD_WFC_TIMEOUT_MAX) {
fprintf(stderr, "wfc_timeout => %d"
" out of range [%d..%d]\n",
timeo_ctx.wfc_timeout,
DRBD_WFC_TIMEOUT_MIN,
DRBD_WFC_TIMEOUT_MAX);
return 20;
}
break;
case 'd':
timeo_ctx.degr_wfc_timeout = m_strtoll(optarg, 1);
if(DRBD_DEGR_WFC_TIMEOUT_MIN > timeo_ctx.degr_wfc_timeout ||
timeo_ctx.degr_wfc_timeout > DRBD_DEGR_WFC_TIMEOUT_MAX) {
fprintf(stderr, "degr_wfc_timeout => %d"
" out of range [%d..%d]\n",
timeo_ctx.degr_wfc_timeout,
DRBD_DEGR_WFC_TIMEOUT_MIN,
DRBD_DEGR_WFC_TIMEOUT_MAX);
return 20;
}
break;
case 'o':
timeo_ctx.outdated_wfc_timeout = m_strtoll(optarg, 1);
if(DRBD_OUTDATED_WFC_TIMEOUT_MIN > timeo_ctx.outdated_wfc_timeout ||
timeo_ctx.outdated_wfc_timeout > DRBD_OUTDATED_WFC_TIMEOUT_MAX) {
fprintf(stderr, "outdated_wfc_timeout => %d"
" out of range [%d..%d]\n",
timeo_ctx.outdated_wfc_timeout,
DRBD_OUTDATED_WFC_TIMEOUT_MIN,
DRBD_OUTDATED_WFC_TIMEOUT_MAX);
return 20;
}
break;
case 'n':
opt_now = true;
break;
case 'p':
opt_poll = true;
break;
case 's':
opt_verbose = true;
opt_statistics = true;
break;
case 'w':
if (!optarg || !strcmp(optarg, "yes"))
wait_after_split_brain = true;
break;
case 'D':
show_defaults = true;
break;
case 'T':
opt_timestamps = true;
break;
case 'c':
if (!parse_color_argument())
print_usage_and_exit("unknown --color argument");
break;
}
}
if (optind < argc) {
warn_print_excess_args(argc, argv, optind + 1);
return 20;
}
timeout_ms = -1;
if (cm->show_function == &wait_for_family) {
struct peer_devices_list *peer_device;
struct msg_buff *smsg;
struct iovec iov;
int rr;
char *res_name = cm->ctx_key & CTX_RESOURCE ? objname : "all";
peer_devices = list_peer_devices(res_name);
/* if there are no peer devices, we don't wait by definition */
if (!peer_devices)
return 0;
iov.iov_len = DEFAULT_MSG_SIZE;
iov.iov_base = malloc(iov.iov_len);
smsg = msg_new(DEFAULT_MSG_SIZE);
if (!smsg || !iov.iov_base) {
msg_free(smsg);
free(iov.iov_base);
fprintf(stderr, "could not allocate netlink messages\n");
return 20;
}
timeo_ctx.smsg = smsg;
timeo_ctx.iov = &iov;
for (peer_device = peer_devices;
peer_device;
peer_device = peer_device->next) {
timeo_ctx.ctx = peer_device->ctx;
rr = choose_timeout(&timeo_ctx);
if (rr)
return rr;
peer_device->timeout_ms =
timeo_ctx.timeout ? timeo_ctx.timeout * 1000 : -1;
/* rewind send message buffer */
smsg->tail = smsg->data;
}
msg_free(smsg);
free(iov.iov_base);
timeout_ms = MULTIPLE_TIMEOUTS;
}
if (!cm->continuous_poll)
timeout_ms = 120000; /* normal "get" request, or "show" */
err = generic_get(cm, timeout_ms, peer_devices);
if (cm->show_function == &print_notifications &&
opt_now && opt_poll) { /* events2 --now --poll */
while ( (c = fgetc(stdin)) != EOF) {
switch (c) {
case 'n': /* now */
err = generic_get(cm, timeout_ms, peer_devices);
break;
case '\n':
break;
default:
goto out_polling;
}
}
out_polling:;
}
free_peer_devices(peer_devices);
return err;
}
static bool options_empty(struct nlattr *attr, struct context_def *ctx)
{
struct field_def *field;
if (!attr)
return true;
if (drbd_nla_parse_nested(nested_attr_tb, ctx->nla_policy_size - 1,
attr, ctx->nla_policy)) {
fprintf(stderr, "nla_policy violation\n");
}
for (field = ctx->fields; field->name; field++) {
struct nlattr *nlattr;
const char *str;
bool is_default;
nlattr = ntb(field->nla_type);
if (!nlattr)
continue;
str = field->ops->get(ctx, field, nlattr);
is_default = field->ops->is_default(field, str);
if (is_default && !show_defaults)
continue;
return false;
}
return true;
}
static void show_peer_device(struct peer_devices_list *peer_device)
{
if (options_empty(peer_device->peer_device_conf, &peer_device_options_ctx))
return;
printI("volume %d {\n", peer_device->ctx.ctx_volume);
++indent;
print_options(peer_device->peer_device_conf, &peer_device_options_ctx, "disk");
--indent;
printI("}\n");
}
static void print_paths(struct connections_list *connection)
{
char address[ADDRESS_STR_MAX];
char *colon;
struct nlattr *nla;
int tmp;
if (!connection->path_list)
return;
nla_for_each_nested(nla, connection->path_list, tmp) {
int l = nla_len(nla);
if (!address_str(address, nla_data(nla), l))
continue;
colon = strchr(address, ':');
if (colon)
*colon = ' ';
if (nla->nla_type == T_my_addr) {
printI("path {\n");
++indent;
printI("_this_host %s;\n", address);
}
if (nla->nla_type == T_peer_addr) {
printI("_remote_host %s;\n", address);
--indent;
printI("}\n");
}
}
}
static void show_connection(struct connections_list *connection, struct peer_devices_list *peer_devices)
{
struct peer_devices_list *peer_device;
printI("connection {\n");
++indent;
printI("_peer_node_id %d;\n", connection->ctx.ctx_peer_node_id);
print_paths(connection);
if (connection->info.conn_connection_state == C_STANDALONE)
printI("_is_standalone;\n");
print_options(connection->net_conf, &show_net_options_ctx, "net");
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (connection->ctx.ctx_peer_node_id == peer_device->ctx.ctx_peer_node_id)
show_peer_device(peer_device);
}
--indent;
printI("}\n");
}
static void show_volume(struct devices_list *device)
{
printI("volume %d {\n", device->ctx.ctx_volume);
++indent;
printI("device\t\t\tminor %d;\n", device->minor);
if (device->disk_conf.backing_dev[0]) {
printI("disk\t\t\t\"%s\";\n", device->disk_conf.backing_dev);
printI("meta-disk\t\t\t");
switch(device->disk_conf.meta_dev_idx) {
case DRBD_MD_INDEX_INTERNAL:
case DRBD_MD_INDEX_FLEX_INT:
printf("internal;\n");
break;
case DRBD_MD_INDEX_FLEX_EXT:
printf("%s;\n",
double_quote_string(device->disk_conf.meta_dev));
break;
default:
printf("%s [ %d ];\n",
double_quote_string(device->disk_conf.meta_dev),
device->disk_conf.meta_dev_idx);
}
} else if (device->info.is_intentional_diskless == 1) {
printI("disk\t\t\tnone;\n");
}
print_options(device->disk_conf_nl, &attach_cmd_ctx, "disk");
--indent;
printI("}\n"); /* close volume */
}
static int show_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct resources_list *resources_list, *resource;
char *old_objname = objname;
int c;
optind = 0; /* reset getopt_long() */
for (;;) {
c = getopt_long(argc, argv, "D", show_cmd_options, 0);
if (c == -1)
break;
switch(c) {
default:
case '?':
return 20;
case 'D':
show_defaults = true;
break;
}
}
resources_list = sort_resources(list_resources());
if (resources_list == NULL)
printf("# No currently configured DRBD found.\n");
for (resource = resources_list; resource; resource = resource->next) {
struct devices_list *devices, *device;
struct connections_list *connections, *connection;
struct peer_devices_list *peer_devices = NULL;
struct nlattr *nla;
if (strcmp(old_objname, "all") && strcmp(old_objname, resource->name))
continue;
devices = list_devices(resource->name);
connections = sort_connections(list_connections(resource->name));
if (devices && connections)
peer_devices = list_peer_devices(resource->name);
objname = resource->name;
printI("resource %s {\n", resource->name);
++indent;
print_options(resource->res_opts, &resource_options_ctx, "options");
printI("_this_host {\n");
++indent;
nla = nla_find_nested(resource->res_opts, __nla_type(T_node_id));
if (nla)
printI("node-id\t\t\t%d;\n", *(uint32_t *)nla_data(nla));
for (device = devices; device; device = device->next)
show_volume(device);
--indent;
printI("}\n");
for (connection = connections; connection; connection = connection->next)
show_connection(connection, peer_devices);
--indent;
printI("}\n\n");
free_connections(connections);
free_devices(devices);
free_peer_devices(peer_devices);
}
free(resources_list);
objname = old_objname;
return 0;
}
static const char *susp_str(struct resource_info *info)
{
static char buffer[32];
*buffer = 0;
if (info->res_susp)
strcat(buffer, ",user" + (*buffer == 0));
if (info->res_susp_nod)
strcat(buffer, ",no-data" + (*buffer == 0));
if (info->res_susp_fen)
strcat(buffer, ",fencing" + (*buffer == 0));
if (info->res_susp_quorum)
strcat(buffer, ",quorum" + (*buffer == 0));
if (*buffer == 0)
strcat(buffer, "no");
return buffer;
}
int nowrap_printf(int indent, const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = vprintf(format, ap);
va_end(ap);
return ret;
}
void print_resource_statistics(int indent,
struct resource_statistics *old,
struct resource_statistics *new,
int (*wrap_printf)(int, const char *, ...))
{
static const char *write_ordering_str[] = {
[WO_NONE] = "none",
[WO_DRAIN_IO] = "drain",
[WO_BDEV_FLUSH] = "flush",
[WO_BIO_BARRIER] = "barrier",
};
uint32_t wo = new->res_stat_write_ordering;
if ((!old ||
old->res_stat_write_ordering != wo) &&
wo < ARRAY_SIZE(write_ordering_str) &&
write_ordering_str[wo]) {
wrap_printf(indent, " write-ordering:%s", write_ordering_str[wo]);
}
}
void print_device_statistics(int indent,
struct device_statistics *old,
struct device_statistics *new,
int (*wrap_printf)(int, const char *, ...))
{
if (opt_statistics) {
if (opt_verbose)
wrap_printf(indent, " size:" U64,
(uint64_t)new->dev_size / 2);
wrap_printf(indent, " read:" U64,
(uint64_t)new->dev_read / 2);
wrap_printf(indent, " written:" U64,
(uint64_t)new->dev_write / 2);
if (opt_verbose) {
wrap_printf(indent, " al-writes:" U64,
(uint64_t)new->dev_al_writes);
wrap_printf(indent, " bm-writes:" U64,
(uint64_t)new->dev_bm_writes);
wrap_printf(indent, " upper-pending:" U32,
new->dev_upper_pending);
wrap_printf(indent, " lower-pending:" U32,
new->dev_lower_pending);
if (!old ||
old->dev_al_suspended != new->dev_al_suspended)
wrap_printf(indent, " al-suspended:%s",
new->dev_al_suspended ? "yes" : "no");
}
}
if ((!old ||
old->dev_upper_blocked != new->dev_upper_blocked ||
old->dev_lower_blocked != new->dev_lower_blocked) &&
new->dev_size != -1 &&
(opt_verbose ||
new->dev_upper_blocked ||
new->dev_lower_blocked)) {
const char *x1 = "", *x2 = "";
bool first = true;
if (new->dev_upper_blocked) {
x1 = ",upper" + first;
first = false;
}
if (new->dev_lower_blocked) {
x2 = ",lower" + first;
first = false;
}
if (first)
x1 = "no";
wrap_printf(indent, " blocked:%s%s", x1, x2);
}
}
void print_connection_statistics(int indent,
struct connection_statistics *old,
struct connection_statistics *new,
int (*wrap_printf)(int, const char *, ...))
{
if (!old ||
old->conn_congested != new->conn_congested)
wrap_printf(indent, " congested:%s", new->conn_congested ? "yes" : "no");
}
static void peer_device_status_json(struct peer_devices_list *peer_device)
{
struct peer_device_statistics *s = &peer_device->statistics;
bool in_rsync = (peer_device->info.peer_repl_state >= L_SYNC_SOURCE &&
peer_device->info.peer_repl_state <= L_PAUSED_SYNC_T);
printf(" {\n"
" \"volume\": %d,\n"
" \"replication-state\": \"%s\",\n"
" \"peer-disk-state\": \"%s\",\n"
" \"peer-client\": \"%s\",\n"
" \"resync-suspended\": \"%s\",\n"
" \"received\": " U64 ",\n"
" \"sent\": " U64 ",\n"
" \"out-of-sync\": " U64 ",\n"
" \"pending\": " U32 ",\n"
" \"unacked\": " U32 "%s\n",
peer_device->ctx.ctx_volume,
drbd_repl_str(peer_device->info.peer_repl_state),
drbd_disk_str(peer_device->info.peer_disk_state),
peer_intentional_diskless_str(&peer_device->info),
resync_susp_str(&peer_device->info),
(uint64_t)s->peer_dev_received / 2,
(uint64_t)s->peer_dev_sent / 2,
(uint64_t)s->peer_dev_out_of_sync / 2,
s->peer_dev_pending,
s->peer_dev_unacked,
in_rsync ? "," : "");
if (in_rsync)
printf(" \"resync-done\": %.2f\n",
100 * (1 - (double)peer_device->statistics.peer_dev_out_of_sync /
(double)peer_device->device->statistics.dev_size));
printf(" }");
}
static void connection_status_json(struct connections_list *connection,
struct peer_devices_list *peer_devices)
{
struct peer_devices_list *peer_device;
int i = 0;
printf(" {\n"
" \"peer-node-id\": %d,\n"
" \"name\": \"%s\",\n"
" \"connection-state\": \"%s\", \n"
" \"congested\": %s,\n"
" \"peer-role\": \"%s\",\n"
" \"peer_devices\": [\n",
connection->ctx.ctx_peer_node_id,
connection->ctx.ctx_conn_name,
drbd_conn_str(connection->info.conn_connection_state),
connection->statistics.conn_congested ? "true" : "false",
drbd_role_str(connection->info.conn_role));
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (connection->ctx.ctx_peer_node_id != peer_device->ctx.ctx_peer_node_id)
continue;
if (i)
puts(",");
peer_device_status_json(peer_device);
i++;
}
printf(" ]\n }");
}
static void device_status_json(struct devices_list *device)
{
enum drbd_disk_state disk_state = device->info.dev_disk_state;
bool d_statistics = (device->statistics.dev_size != -1);
printf(" {\n"
" \"volume\": %d,\n"
" \"minor\": %d,\n"
" \"disk-state\": \"%s\",\n"
" \"client\": \"%s\"%s\n",
device->ctx.ctx_volume,
device->minor,
drbd_disk_str(disk_state),
intentional_diskless_str(&device->info),
d_statistics ? "," : "");
if (d_statistics) {
struct device_statistics *s = &device->statistics;
printf(" \"size\": " U64 ",\n"
" \"read\": " U64 ",\n"
" \"written\": " U64 ",\n"
" \"al-writes\": " U64 ",\n"
" \"bm-writes\": " U64 ",\n"
" \"upper-pending\": " U32 ",\n"
" \"lower-pending\": " U32 "\n",
(uint64_t)s->dev_size / 2,
(uint64_t)s->dev_read / 2,
(uint64_t)s->dev_write / 2,
(uint64_t)s->dev_al_writes,
(uint64_t)s->dev_bm_writes,
s->dev_upper_pending,
s->dev_lower_pending);
}
printf(" }");
}
static void resource_status_json(struct resources_list *resource)
{
static const char *write_ordering_str[] = {
[WO_NONE] = "none",
[WO_DRAIN_IO] = "drain",
[WO_BDEV_FLUSH] = "flush",
[WO_BIO_BARRIER] = "barrier",
};
struct nlattr *nla;
int node_id = -1;
bool suspended =
resource->info.res_susp ||
resource->info.res_susp_nod ||
resource->info.res_susp_fen ||
resource->info.res_susp_quorum;
nla = nla_find_nested(resource->res_opts, __nla_type(T_node_id));
if (nla)
node_id = *(uint32_t *)nla_data(nla);
printf("{\n"
" \"name\": \"%s\",\n"
" \"node-id\": %d,\n"
" \"role\": \"%s\",\n"
" \"suspended\": %s,\n"
" \"write-ordering\": \"%s\",\n"
" \"devices\": [\n",
resource->name,
node_id,
drbd_role_str(resource->info.res_role),
suspended ? "true" : "false",
write_ordering_str[resource->statistics.res_stat_write_ordering]);
}
void print_peer_device_statistics(int indent,
struct peer_device_statistics *old,
struct peer_device_statistics *new,
int (*wrap_printf)(int, const char *, ...))
{
wrap_printf(indent, " received:" U64,
(uint64_t)new->peer_dev_received / 2);
wrap_printf(indent, " sent:" U64,
(uint64_t)new->peer_dev_sent / 2);
if (opt_verbose || new->peer_dev_out_of_sync)
wrap_printf(indent, " out-of-sync:" U64,
(uint64_t)new->peer_dev_out_of_sync / 2);
if (opt_verbose) {
wrap_printf(indent, " pending:" U32,
new->peer_dev_pending);
wrap_printf(indent, " unacked:" U32,
new->peer_dev_unacked);
}
}
void resource_status(struct resources_list *resource)
{
enum drbd_role role = resource->info.res_role;
wrap_printf(0, "%s", resource->name);
if (opt_verbose) {
struct nlattr *nla;
nla = nla_find_nested(resource->res_opts, __nla_type(T_node_id));
if (nla)
wrap_printf(4, " node-id:%d", *(uint32_t *)nla_data(nla));
}
wrap_printf(4, " role:%s%s%s",
role_color_start(role, true),
drbd_role_str(role),
role_color_stop(role, true));
if (opt_verbose ||
resource->info.res_susp ||
resource->info.res_susp_nod ||
resource->info.res_susp_fen ||
resource->info.res_susp_quorum)
wrap_printf(4, " suspended:%s", susp_str(&resource->info));
if (opt_statistics && opt_verbose) {
wrap_printf(4, "\n");
print_resource_statistics(4, NULL, &resource->statistics, wrap_printf);
}
wrap_printf(0, "\n");
}
static void device_status(struct devices_list *device, bool single_device)
{
enum drbd_disk_state disk_state = device->info.dev_disk_state;
bool intentional_diskless = device->info.is_intentional_diskless == 1;
int indent = 2;
if (opt_verbose || !(single_device && device->ctx.ctx_volume == 0)) {
wrap_printf(indent, "volume:%u", device->ctx.ctx_volume);
indent = 6;
if (opt_verbose)
wrap_printf(indent, " minor:%u", device->minor);
}
wrap_printf(indent, " disk:%s%s%s",
disk_state_color_start(disk_state, intentional_diskless, true),
drbd_disk_str(disk_state),
disk_state_color_stop(disk_state, true));
if (disk_state == D_DISKLESS && opt_verbose) {
wrap_printf(indent, " client:%s", intentional_diskless_str(&device->info));
}
indent = 6;
if (device->statistics.dev_size != -1) {
if (opt_statistics)
wrap_printf(indent, "\n");
print_device_statistics(indent, NULL, &device->statistics, wrap_printf);
}
wrap_printf(indent, "\n");
}
static const char *_intentionall_diskless_str(unsigned char intentional_diskless) {
switch (intentional_diskless) {
case 0:
return "no";
case 1:
return "yes";
default:
return "unknown";
}
}
static const char *intentional_diskless_str(struct device_info *info)
{
return _intentionall_diskless_str(info->is_intentional_diskless);
}
static const char *peer_intentional_diskless_str(struct peer_device_info *info) {
return _intentionall_diskless_str(info->peer_is_intentional_diskless);
}
static const char *resync_susp_str(struct peer_device_info *info)
{
static char buffer[64];
*buffer = 0;
if (info->peer_resync_susp_user)
strcat(buffer, ",user" + (*buffer == 0));
if (info->peer_resync_susp_peer)
strcat(buffer, ",peer" + (*buffer == 0));
if (info->peer_resync_susp_dependency)
strcat(buffer, ",dependency" + (*buffer == 0));
if (*buffer == 0)
strcat(buffer, "no");
return buffer;
}
static void peer_device_status(struct peer_devices_list *peer_device, bool single_device)
{
int indent = 4;
bool intentional_diskless = peer_device->info.peer_is_intentional_diskless == 1;
if (opt_verbose || !(single_device && peer_device->ctx.ctx_volume == 0)) {
wrap_printf(indent, "volume:%d", peer_device->ctx.ctx_volume);
indent = 8;
}
if (opt_verbose || peer_device->info.peer_repl_state > L_ESTABLISHED) {
enum drbd_repl_state repl_state = peer_device->info.peer_repl_state;
wrap_printf(indent, " replication:%s%s%s",
repl_state_color_start(repl_state),
drbd_repl_str(repl_state),
repl_state_color_stop(repl_state));
indent = 8;
}
if (opt_verbose || opt_statistics ||
peer_device->info.peer_repl_state != L_OFF ||
peer_device->info.peer_disk_state != D_UNKNOWN) {
enum drbd_disk_state disk_state = peer_device->info.peer_disk_state;
wrap_printf(indent, " peer-disk:%s%s%s",
disk_state_color_start(disk_state, intentional_diskless, false),
drbd_disk_str(disk_state),
disk_state_color_stop(disk_state, false));
if (disk_state == D_DISKLESS && opt_verbose)
wrap_printf(indent, " peer-client:%s", peer_intentional_diskless_str(&peer_device->info));
indent = 8;
if (peer_device->info.peer_repl_state >= L_SYNC_SOURCE &&
peer_device->info.peer_repl_state <= L_PAUSED_SYNC_T) {
wrap_printf(indent, " done:%.2f", 100 * (1 -
(double)peer_device->statistics.peer_dev_out_of_sync /
(double)peer_device->device->statistics.dev_size));
}
if (opt_verbose ||
peer_device->info.peer_resync_susp_user ||
peer_device->info.peer_resync_susp_peer ||
peer_device->info.peer_resync_susp_dependency)
wrap_printf(indent, " resync-suspended:%s",
resync_susp_str(&peer_device->info));
if (opt_statistics && peer_device->statistics.peer_dev_received != -1) {
wrap_printf(indent, "\n");
print_peer_device_statistics(indent, NULL, &peer_device->statistics, wrap_printf);
}
}
wrap_printf(0, "\n");
}
static void peer_devices_status(struct drbd_cfg_context *ctx, struct peer_devices_list *peer_devices, bool single_device)
{
struct peer_devices_list *peer_device;
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (ctx->ctx_peer_node_id != peer_device->ctx.ctx_peer_node_id)
continue;
peer_device_status(peer_device, single_device);
}
}
static void connection_status(struct connections_list *connection,
struct peer_devices_list *peer_devices,
bool single_device)
{
if (connection->ctx.ctx_conn_name_len)
wrap_printf(2, "%s", connection->ctx.ctx_conn_name);
if (opt_verbose || connection->ctx.ctx_conn_name_len == 0) {
int in = connection->ctx.ctx_conn_name_len ? 6 : 2;
wrap_printf(in, " node-id:%d", connection->ctx.ctx_peer_node_id);
}
if (opt_verbose || connection->info.conn_connection_state != C_CONNECTED) {
enum drbd_conn_state cstate = connection->info.conn_connection_state;
wrap_printf(6, " connection:%s%s%s",
cstate_color_start(cstate),
drbd_conn_str(cstate),
cstate_color_stop(cstate));
}
if (opt_verbose || connection->info.conn_connection_state == C_CONNECTED) {
enum drbd_role role = connection->info.conn_role;
wrap_printf(6, " role:%s%s%s",
role_color_start(role, false),
drbd_role_str(role),
role_color_stop(role, false));
}
if (opt_verbose || connection->statistics.conn_congested > 0)
print_connection_statistics(6, NULL, &connection->statistics, wrap_printf);
wrap_printf(0, "\n");
if (opt_verbose || opt_statistics || connection->info.conn_connection_state == C_CONNECTED)
peer_devices_status(&connection->ctx, peer_devices, single_device);
}
static void stop_colors(int sig)
{
printf("%s", stop_color_code());
signal(sig, SIG_DFL);
raise(sig);
}
static void link_peer_devices_to_devices(struct peer_devices_list *peer_devices, struct devices_list *devices)
{
struct peer_devices_list *peer_device;
struct devices_list *device;
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
for (device = devices; device; device = device->next) {
if (peer_device->ctx.ctx_volume == device->ctx.ctx_volume) {
peer_device->device = device;
break;
}
}
}
}
static int status_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct resources_list *resources, *resource;
struct sigaction sa = {
.sa_handler = stop_colors,
.sa_flags = SA_RESETHAND,
};
bool found = false;
bool json = false;
int c;
optind = 0; /* reset getopt_long() */
for (;;) {
c = getopt_long(argc, argv, make_optstring(cm->options), cm->options, 0);
if (c == -1)
break;
switch(c) {
default:
case '?':
return 20;
case 'v':
opt_verbose = true;
break;
case 's':
opt_statistics = true;
break;
case 'c':
if (!parse_color_argument())
print_usage_and_exit("unknown --color argument");
break;
case 'j':
json = true;
break;
}
}
resources = sort_resources(list_resources());
if (resources == NULL)
printf("# No currently configured DRBD found.\n");
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
if (json)
puts("[");
for (resource = resources; resource; resource = resource->next) {
struct devices_list *devices, *device;
struct connections_list *connections, *connection;
struct peer_devices_list *peer_devices = NULL;
bool single_device;
static bool jsonisfirst = true;
if (strcmp(objname, "all") && strcmp(objname, resource->name))
continue;
if (json)
jsonisfirst ? jsonisfirst = false : puts(",");
devices = list_devices(resource->name);
connections = sort_connections(list_connections(resource->name));
if (devices && connections)
peer_devices = list_peer_devices(resource->name);
link_peer_devices_to_devices(peer_devices, devices);
if (json) {
resource_status_json(resource);
for (device = devices; device; device = device->next) {
device_status_json(device);
if (device->next)
puts(",");
}
puts(" ],\n \"connections\": [");
for (connection = connections; connection; connection = connection->next) {
connection_status_json(connection, peer_devices);
if (connection->next)
puts(",");
}
puts(" ]\n}");
} else {
resource_status(resource);
single_device = devices && !devices->next;
for (device = devices; device; device = device->next)
device_status(device, single_device);
for (connection = connections; connection; connection = connection->next)
connection_status(connection, peer_devices, single_device);
wrap_printf(0, "\n");
}
free_connections(connections);
free_devices(devices);
free_peer_devices(peer_devices);
found = true;
}
if (json)
puts("]\n");
free_resources(resources);
if (!found && strcmp(objname, "all")) {
fprintf(stderr, "%s: No such resource\n", objname);
return 10;
}
return 0;
}
static int role_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct resources_list *resources, *resource;
int ret = ERR_RES_NOT_KNOWN;
resources = list_resources();
for (resource = resources; resource; resource = resource->next) {
if (strcmp(objname, resource->name))
continue;
printf("%s\n", drbd_role_str(resource->info.res_role));
ret = NO_ERROR;
break;
}
free_resources(resources);
if (ret != NO_ERROR) {
fprintf(stderr, "%s: %s\n", objname, error_to_string(ret));
return 10;
}
return 0;
}
static int cstate_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct connections_list *connections, *connection;
bool found = false;
connections = list_connections(objname);
for (connection = connections; connection; connection = connection->next) {
if (connection->ctx.ctx_peer_node_id != global_ctx.ctx_peer_node_id)
continue;
printf("%s\n", drbd_conn_str(connection->info.conn_connection_state));
found = true;
break;
}
free_connections(connections);
if (!found) {
fprintf(stderr, "%s: No such connection\n", objname);
return 10;
}
return 0;
}
static int dstate_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct devices_list *devices, *device;
bool found = false;
struct peer_devices_list *peer_devices, *peer_device;
devices = list_devices(NULL);
for (device = devices; device; device = device->next) {
if (device->minor != minor)
continue;
printf("%s", drbd_disk_str(device->info.dev_disk_state));
/* printf("%s/%s\n",drbd_disk_str(state.disk),drbd_disk_str(state.pdsk)); */
peer_devices = list_peer_devices(device->ctx.ctx_resource_name);
for (peer_device = peer_devices;
peer_device;
peer_device = peer_device->next) {
if (device->ctx.ctx_volume == peer_device->ctx.ctx_volume)
printf("/%s", drbd_disk_str(peer_device->info.peer_disk_state));
}
printf("\n");
found = true;
break;
}
free_devices(devices);
if (!found) {
fprintf(stderr, "%s: No such device\n", objname);
return 10;
}
return 0;
}
static char *af_to_str(int af)
{
if (af == AF_INET)
return "ipv4";
else if (af == AF_INET6)
return "ipv6";
/* AF_SSOCKS typically is 27, the same as AF_INET_SDP.
* But with warn_and_use_default = 0, it will stay at -1 if not available.
* Just keep the test on ssocks before the one on SDP (which is hard-coded),
* and all should be fine. */
else if (af == get_af_ssocks(0))
return "ssocks";
else if (af == AF_INET_SDP)
return "sdp";
else return "unknown";
}
static char *address_str(char *buffer, void* address, int addr_len)
{
union {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
} a;
/* avoid alignment issues on certain platforms (e.g. armel) */
memset(&a, 0, sizeof(a));
memcpy(&a.addr, address, addr_len);
if (a.addr.sa_family == AF_INET
|| a.addr.sa_family == get_af_ssocks(0)
|| a.addr.sa_family == AF_INET_SDP) {
snprintf(buffer, ADDRESS_STR_MAX, "%s:%s:%u",
af_to_str(a.addr4.sin_family),
inet_ntoa(a.addr4.sin_addr),
ntohs(a.addr4.sin_port));
return buffer;
} else if (a.addr.sa_family == AF_INET6) {
char buf2[ADDRESS_STR_MAX];
int n;
buf2[0] = 0;
/* inet_ntop does not include scope info */
getnameinfo(&a.addr, addr_len, buf2, sizeof(buf2),
NULL, 0, NI_NUMERICHOST|NI_NUMERICSERV);
n = snprintf(buffer, ADDRESS_STR_MAX, "%s:[%s]:%u",
af_to_str(a.addr6.sin6_family), buf2,
ntohs(a.addr6.sin6_port));
assert(n > 0);
assert(n < ADDRESS_STR_MAX); /* there should be no need to truncate */
return buffer;
} else
return NULL;
}
static int remember_resource(struct drbd_cmd *cmd, struct genl_info *info, void *u_ptr)
{
struct resources_list ***tail = u_ptr;
struct drbd_cfg_context cfg = { .ctx_volume = -1U, .ctx_peer_node_id = -1U };
if (!info)
return 0;
drbd_cfg_context_from_attrs(&cfg, info);
if (cfg.ctx_resource_name) {
struct resources_list *r = calloc(1, sizeof(*r));
struct nlattr *res_opts = global_attrs[DRBD_NLA_RESOURCE_OPTS];
r->name = strdup(cfg.ctx_resource_name);
if (res_opts) {
int size = nla_total_size(nla_len(res_opts));
r->res_opts = malloc(size);
memcpy(r->res_opts, res_opts, size);
}
resource_info_from_attrs(&r->info, info);
memset(&r->statistics, -1, sizeof(r->statistics));
resource_statistics_from_attrs(&r->statistics, info);
**tail = r;
*tail = &r->next;
}
return 0;
}
static void free_resources(struct resources_list *resources)
{
while (resources) {
struct resources_list *r = resources;
resources = resources->next;
free(r->name);
free(r->res_opts);
free(r);
}
}
static int resource_name_cmp(const struct resources_list * const *a, const struct resources_list * const *b)
{
return strcmp((*a)->name, (*b)->name);
}
static struct resources_list *sort_resources(struct resources_list *resources)
{
struct resources_list *r;
int n;
for (r = resources, n = 0; r; r = r->next)
n++;
if (n > 1) {
struct resources_list **array;
array = malloc(sizeof(*array) * n);
for (r = resources, n = 0; r; r = r->next)
array[n++] = r;
qsort(array, n, sizeof(*array), (int (*)(const void *, const void *)) resource_name_cmp);
n--;
array[n]->next = NULL;
for (; n > 0; n--)
array[n - 1]->next = array[n];
resources = array[0];
free(array);
}
return resources;
}
/*
* Expects objname to be set to the resource name or "all".
*/
static struct resources_list *list_resources(void)
{
struct drbd_cmd cmd = {
.cmd_id = DRBD_ADM_GET_RESOURCES,
.show_function = remember_resource,
.missing_ok = false,
};
struct resources_list *list = NULL, **tail = &list;
char *old_objname = objname;
unsigned old_minor = minor;
int old_my_addr_len = global_ctx.ctx_my_addr_len;
int old_peer_addr_len = global_ctx.ctx_peer_addr_len;
int err;
objname = "all";
minor = -1;
global_ctx.ctx_my_addr_len = 0;
global_ctx.ctx_peer_addr_len = 0;
err = generic_get(&cmd, 120000, &tail);
objname = old_objname;
minor = old_minor;
global_ctx.ctx_my_addr_len = old_my_addr_len;
global_ctx.ctx_peer_addr_len = old_peer_addr_len;
if (err) {
free_resources(list);
list = NULL;
}
return list;
}
static int remember_device(struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
struct devices_list ***tail = u_ptr;
struct drbd_cfg_context ctx = { .ctx_volume = -1U, .ctx_peer_node_id = -1U };
if (!info)
return 0;
drbd_cfg_context_from_attrs(&ctx, info);
if (ctx.ctx_volume != -1U) {
struct devices_list *d = calloc(1, sizeof(*d));
struct nlattr *disk_conf_nl = global_attrs[DRBD_NLA_DISK_CONF];
d->minor = ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
d->ctx = ctx;
if (disk_conf_nl) {
int size = nla_total_size(nla_len(disk_conf_nl));
d->disk_conf_nl = malloc(size);
memcpy(d->disk_conf_nl, disk_conf_nl, size);
}
disk_conf_from_attrs(&d->disk_conf, info);
d->info.dev_disk_state = D_DISKLESS;
d->info.is_intentional_diskless = IS_INTENTIONAL_DEF;
device_info_from_attrs(&d->info, info);
memset(&d->statistics, -1, sizeof(d->statistics));
device_statistics_from_attrs(&d->statistics, info);
**tail = d;
*tail = &d->next;
}
return 0;
}
/*
* Expects objname to be set to the resource name or "all".
*/
static struct devices_list *list_devices(char *resource_name)
{
struct drbd_cmd cmd = {
.cmd_id = DRBD_ADM_GET_DEVICES,
.show_function = remember_device,
.missing_ok = false,
};
struct devices_list *list = NULL, **tail = &list;
char *old_objname = objname;
unsigned old_minor = minor;
int old_my_addr_len = global_ctx.ctx_my_addr_len;
int old_peer_addr_len = global_ctx.ctx_peer_addr_len;
int err;
objname = resource_name ? resource_name : "all";
minor = -1;
global_ctx.ctx_my_addr_len = 0;
global_ctx.ctx_peer_addr_len = 0;
err = generic_get(&cmd, 120000, &tail);
objname = old_objname;
minor = old_minor;
global_ctx.ctx_my_addr_len = old_my_addr_len;
global_ctx.ctx_peer_addr_len = old_peer_addr_len;
if (err) {
free_devices(list);
list = NULL;
}
return list;
}
static void free_devices(struct devices_list *devices)
{
while (devices) {
struct devices_list *d = devices;
devices = devices->next;
free(d->disk_conf_nl);
free(d);
}
}
static int remember_connection(struct drbd_cmd *cmd, struct genl_info *info, void *u_ptr)
{
struct connections_list ***tail = u_ptr;
struct drbd_cfg_context ctx = { .ctx_volume = -1U, .ctx_peer_node_id = -1U };
if (!info)
return 0;
drbd_cfg_context_from_attrs(&ctx, info);
if (ctx.ctx_resource_name) {
struct connections_list *c = calloc(1, sizeof(*c));
struct nlattr *net_conf = global_attrs[DRBD_NLA_NET_CONF];
struct nlattr *path_list = global_attrs[DRBD_NLA_PATH_PARMS];
c->ctx = ctx;
if (net_conf) {
int size = nla_total_size(nla_len(net_conf));
c->net_conf = malloc(size);
memcpy(c->net_conf, net_conf, size);
}
if (path_list) {
int size = nla_total_size(nla_len(path_list));
c->path_list = malloc(size);
memcpy(c->path_list, path_list, size);
}
connection_info_from_attrs(&c->info, info);
memset(&c->statistics, -1, sizeof(c->statistics));
connection_statistics_from_attrs(&c->statistics, info);
**tail = c;
*tail = &c->next;
}
return 0;
}
static int connection_name_cmp(const struct connections_list * const *a, const struct connections_list * const *b)
{
if (!(*a)->ctx.ctx_conn_name_len != !(*b)->ctx.ctx_conn_name_len)
return !(*b)->ctx.ctx_conn_name_len;
return strcmp((*a)->ctx.ctx_conn_name, (*b)->ctx.ctx_conn_name);
}
static struct connections_list *sort_connections(struct connections_list *connections)
{
struct connections_list *c;
int n;
for (c = connections, n = 0; c; c = c->next)
n++;
if (n > 1) {
struct connections_list **array;
array = malloc(sizeof(*array) * n);
for (c = connections, n = 0; c; c = c->next)
array[n++] = c;
qsort(array, n, sizeof(*array), (int (*)(const void *, const void *)) connection_name_cmp);
n--;
array[n]->next = NULL;
for (; n > 0; n--)
array[n - 1]->next = array[n];
connections = array[0];
free(array);
}
return connections;
}
/*
* Expects objname to be set to the resource name or "all".
*/
static struct connections_list *list_connections(char *resource_name)
{
struct drbd_cmd cmd = {
.cmd_id = DRBD_ADM_GET_CONNECTIONS,
.show_function = remember_connection,
.missing_ok = true,
};
struct connections_list *list = NULL, **tail = &list;
char *old_objname = objname;
unsigned old_minor = minor;
int old_my_addr_len = global_ctx.ctx_my_addr_len;
int old_peer_addr_len = global_ctx.ctx_peer_addr_len;
int err;
objname = resource_name ? resource_name : "all";
minor = -1;
global_ctx.ctx_my_addr_len = 0;
global_ctx.ctx_peer_addr_len = 0;
err = generic_get(&cmd, 120000, &tail);
objname = old_objname;
minor = old_minor;
global_ctx.ctx_my_addr_len = old_my_addr_len;
global_ctx.ctx_peer_addr_len = old_peer_addr_len;
if (err) {
free_connections(list);
list = NULL;
}
return list;
}
static void free_connections(struct connections_list *connections)
{
while (connections) {
struct connections_list *l = connections;
connections = connections->next;
free(l->net_conf);
free(l);
}
}
static int remember_peer_device(struct drbd_cmd *cmd, struct genl_info *info, void *u_ptr)
{
struct peer_devices_list ***tail = u_ptr;
struct drbd_cfg_context ctx = { .ctx_volume = -1U, .ctx_peer_node_id = -1U };
if (!info)
return 0;
drbd_cfg_context_from_attrs(&ctx, info);
if (ctx.ctx_resource_name) {
struct peer_devices_list *p = calloc(1, sizeof(*p));
struct nlattr *peer_device_conf = global_attrs[DRBD_NLA_PEER_DEVICE_OPTS];
if (!p)
exit(20);
p->ctx = ctx;
if (peer_device_conf) {
int size = nla_total_size(nla_len(peer_device_conf));
p->peer_device_conf = malloc(size);
memcpy(p->peer_device_conf, peer_device_conf, size);
}
p->info.peer_is_intentional_diskless = IS_INTENTIONAL_DEF;
peer_device_info_from_attrs(&p->info, info);
memset(&p->statistics, -1, sizeof(p->statistics));
peer_device_statistics_from_attrs(&p->statistics, info);
**tail = p;
*tail = &p->next;
}
return 0;
}
/*
* Expects objname to be set to the resource name or "all".
*/
static struct peer_devices_list *list_peer_devices(char *resource_name)
{
struct drbd_cmd cmd = {
.cmd_id = DRBD_ADM_GET_PEER_DEVICES,
.show_function = remember_peer_device,
.missing_ok = false,
};
struct peer_devices_list *list = NULL, **tail = &list;
char *old_objname = objname;
unsigned old_minor = minor;
int old_my_addr_len = global_ctx.ctx_my_addr_len;
int old_peer_addr_len = global_ctx.ctx_peer_addr_len;
int err;
objname = resource_name ? resource_name : "all";
minor = -1;
global_ctx.ctx_my_addr_len = 0;
global_ctx.ctx_peer_addr_len = 0;
err = generic_get(&cmd, 120000, &tail);
objname = old_objname;
minor = old_minor;
global_ctx.ctx_my_addr_len = old_my_addr_len;
global_ctx.ctx_peer_addr_len = old_peer_addr_len;
if (err) {
free_peer_devices(list);
list = NULL;
}
return list;
}
static void free_peer_devices(struct peer_devices_list *peer_devices)
{
while (peer_devices) {
struct peer_devices_list *p = peer_devices;
peer_devices = peer_devices->next;
free(p->peer_device_conf);
free(p);
}
}
static int check_resize_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct devices_list *devices, *device;
bool found = false;
bool ret = 0;
devices = list_devices(NULL);
for (device = devices; device; device = device->next) {
struct bdev_info bd = { 0, };
uint64_t bd_size;
int fd;
if (device->minor != minor)
continue;
found = true;
if (!device->disk_conf.backing_dev) {
fprintf(stderr, "Has no disk config, try with drbdmeta.\n");
ret = 1;
break;
}
if (device->disk_conf.meta_dev_idx >= 0 ||
device->disk_conf.meta_dev_idx == DRBD_MD_INDEX_FLEX_EXT) {
lk_bdev_delete(minor);
break;
}
fd = open(device->disk_conf.backing_dev, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "Could not open %s: %m.\n", device->disk_conf.backing_dev);
ret = 1;
break;
}
bd_size = bdev_size(fd);
close(fd);
if (lk_bdev_load(minor, &bd) == 0 &&
bd.bd_size == bd_size &&
bd.bd_name && !strcmp(bd.bd_name, device->disk_conf.backing_dev))
break; /* nothing changed. */
bd.bd_size = bd_size;
bd.bd_name = device->disk_conf.backing_dev;
lk_bdev_save(minor, &bd);
break;
}
free_devices(devices);
if (!found) {
fprintf(stderr, "%s: No such device\n", objname);
return 10;
}
return ret;
}
static bool peer_device_ctx_match(struct drbd_cfg_context *a, struct drbd_cfg_context *b)
{
return
strcmp(a->ctx_resource_name, b->ctx_resource_name) == 0
&& a->ctx_peer_node_id == b->ctx_peer_node_id
&& a->ctx_volume == b->ctx_volume;
}
static int show_or_get_gi_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct peer_devices_list *peer_devices, *peer_device;
struct devices_list *devices = NULL, *device;
uint64_t uuids[UI_SIZE];
int ret = 0, i;
peer_devices = list_peer_devices(NULL);
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next) {
if (!peer_device_ctx_match(&global_ctx, &peer_device->ctx))
continue;
devices = list_devices(peer_device->ctx.ctx_resource_name);
for (device = devices; device; device = device->next) {
if (device->ctx.ctx_volume == global_ctx.ctx_volume)
goto found;
}
}
fprintf(stderr, "%s: No such peer device\n", objname);
ret = 10;
out:
free_devices(devices);
free_peer_devices(peer_devices);
return ret;
found:
if (peer_device->info.peer_repl_state == L_OFF &&
device->info.dev_disk_state == D_DISKLESS) {
fprintf(stderr, "Device is unconfigured\n");
ret = 1;
goto out;
}
if (device->info.dev_disk_state == D_DISKLESS) {
/* XXX we could print the exposed_data_uuid anyways: */
if (0)
printf(X64(016)"\n", (uint64_t)device->statistics.dev_exposed_data_uuid);
fprintf(stderr, "Device has no disk\n");
ret = 1;
goto out;
}
memset(uuids, 0, sizeof(uuids));
uuids[UI_CURRENT] = device->statistics.dev_current_uuid;
uuids[UI_BITMAP] = peer_device->statistics.peer_dev_bitmap_uuid;
i = device->statistics.history_uuids_len / 8;
if (i >= HISTORY_UUIDS_V08)
i = HISTORY_UUIDS_V08 - 1;
for (; i >= 0; i--)
uuids[UI_HISTORY_START + i] =
((uint64_t *)device->statistics.history_uuids)[i];
if(!strcmp(cm->cmd, "show-gi"))
dt_pretty_print_v9_uuids(uuids, device->statistics.dev_disk_flags,
peer_device->statistics.peer_dev_flags);
else
dt_print_v9_uuids(uuids, device->statistics.dev_disk_flags,
peer_device->statistics.peer_dev_flags);
goto out;
}
static int down_cmd(struct drbd_cmd *cm, int argc, char **argv)
{
struct resources_list *resources, *resource;
char *old_objname;
int rv = 0;
if(argc > 2) {
warn_print_excess_args(argc, argv, 2);
return OTHER_ERROR;
}
old_objname = objname;
context = CTX_RESOURCE;
resources = list_resources();
for (resource = resources; resource; resource = resource->next) {
struct devices_list *devices;
int rv2;
if (strcmp(old_objname, "all") && strcmp(old_objname, resource->name))
continue;
objname = resource->name;
devices = list_devices(objname);
rv2 = _generic_config_cmd(cm, argc, argv);
if (!rv2) {
struct devices_list *device;
for (device = devices; device; device = device->next)
unregister_minor(device->minor);
unregister_resource(objname);
}
if (!rv)
rv = rv2;
free_devices(devices);
}
free_resources(resources);
return rv;
}
#define _EVPRINT(checksize, fstr, ...) do { \
ret = snprintf(key + pos, size, fstr, __VA_ARGS__); \
if (ret < 0) \
return ret; \
pos += ret; \
if (size && checksize) \
size -= ret; \
} while(0)
#define EVPRINT(...) _EVPRINT(1, __VA_ARGS__)
/* for llvm static analyzer */
#define EVPRINT_NOSIZE(...) _EVPRINT(0, __VA_ARGS__)
static int event_key(char *key, int size, const char *name, unsigned minor,
struct drbd_cfg_context *ctx)
{
char addr[ADDRESS_STR_MAX];
int ret, pos = 0;
if (!ctx)
return -1;
EVPRINT("%s", name);
if (ctx->ctx_resource_name)
EVPRINT(" name:%s", ctx->ctx_resource_name);
if (ctx->ctx_peer_node_id != -1U)
EVPRINT(" peer-node-id:%d", ctx->ctx_peer_node_id);
if (ctx->ctx_conn_name_len)
EVPRINT(" conn-name:%s", ctx->ctx_conn_name);
if (ctx->ctx_my_addr_len &&
address_str(addr, ctx->ctx_my_addr, ctx->ctx_my_addr_len))
EVPRINT(" local:%s", addr);
if (ctx->ctx_peer_addr_len &&
address_str(addr, ctx->ctx_peer_addr, ctx->ctx_peer_addr_len))
EVPRINT(" peer:%s", addr);
if (ctx->ctx_volume != -1U)
EVPRINT(" volume:%u", ctx->ctx_volume);
if (minor != -1U)
EVPRINT_NOSIZE(" minor:%u", minor);
return pos;
}
static int known_objects_cmp(const void *a, const void *b) {
return strcmp(((const struct entry *)a)->key, ((const struct entry *)b)->key);
}
static void *update_info(char **key, void *value, size_t size)
{
static void *known_objects;
struct entry entry = { .key = *key }, **found;
if (value) {
void *old_value = NULL;
found = tsearch(&entry, &known_objects, known_objects_cmp);
if (*found != &entry)
old_value = (*found)->data;
else {
*found = malloc(sizeof(**found));
if (!*found)
goto fail;
(*found)->key = *key;
*key = NULL;
}
(*found)->data = malloc(size);
if (!(*found)->data)
goto fail;
memcpy((*found)->data, value, size);
return old_value;
} else {
found = tfind(&entry, &known_objects, known_objects_cmp);
if (found) {
struct entry *entry = *found;
tdelete(entry, &known_objects, known_objects_cmp);
free(entry->data);
free(entry->key);
free(entry);
}
return NULL;
}
fail:
perror(progname);
exit(20);
}
static int print_notifications(struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
static const char *action_name[] = {
[NOTIFY_EXISTS] = "exists",
[NOTIFY_CREATE] = "create",
[NOTIFY_CHANGE] = "change",
[NOTIFY_DESTROY] = "destroy",
[NOTIFY_CALL] = "call",
[NOTIFY_RESPONSE] = "response",
};
static char *object_name[] = {
[DRBD_RESOURCE_STATE] = "resource",
[DRBD_DEVICE_STATE] = "device",
[DRBD_CONNECTION_STATE] = "connection",
[DRBD_PEER_DEVICE_STATE] = "peer-device",
[DRBD_HELPER] = "helper",
[DRBD_PATH_STATE] = "path",
};
static uint32_t last_seq;
static bool last_seq_known;
static struct timeval tv;
static bool keep_tv;
struct drbd_cfg_context ctx = { .ctx_volume = -1U, .ctx_peer_node_id = -1U, };
struct drbd_notification_header nh = { .nh_type = -1U };
enum drbd_notification_type action;
struct drbd_genlmsghdr *dh;
char *key = NULL;
if (!info) {
keep_tv = false;
return 0;
}
dh = info->userhdr;
if (dh->ret_code == ERR_MINOR_INVALID && cm->missing_ok)
return 0;
if (dh->ret_code != NO_ERROR)
return dh->ret_code;
if (drbd_notification_header_from_attrs(&nh, info))
return 0;
action = nh.nh_type & ~NOTIFY_FLAGS;
if (action >= ARRAY_SIZE(action_name) ||
!action_name[action]) {
dbg(1, "unknown notification type\n");
goto out;
}
if (opt_now && action != NOTIFY_EXISTS)
return 0;
if (info->genlhdr->cmd != DRBD_INITIAL_STATE_DONE) {
if (drbd_cfg_context_from_attrs(&ctx, info))
return 0;
if (info->genlhdr->cmd >= ARRAY_SIZE(object_name) ||
!object_name[info->genlhdr->cmd]) {
dbg(1, "unknown notification\n");
goto out;
}
}
if (action != NOTIFY_EXISTS) {
if (last_seq_known) {
int skipped = info->nlhdr->nlmsg_seq - (last_seq + 1);
if (skipped)
printf("- skipped %d\n", skipped);
}
last_seq = info->nlhdr->nlmsg_seq;
last_seq_known = true;
}
if (opt_timestamps) {
struct tm *tm;
if (!keep_tv)
gettimeofday(&tv, NULL);
keep_tv = !!(nh.nh_type & NOTIFY_CONTINUES);
tm = localtime(&tv.tv_sec);
printf("%04u-%02u-%02uT%02u:%02u:%02u.%06u%+03d:%02u ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
(int)tv.tv_usec,
(int)(tm->tm_gmtoff / 3600),
(int)((abs(tm->tm_gmtoff) / 60) % 60));
}
if (info->genlhdr->cmd != DRBD_INITIAL_STATE_DONE) {
const char *name = object_name[info->genlhdr->cmd];
int size;
size = event_key(NULL, 0, name, dh->minor, &ctx);
if (size < 0)
goto fail;
key = malloc(size + 1);
if (!key)
goto fail;
event_key(key, size + 1, name, dh->minor, &ctx);
}
printf("%s %s",
action_name[action],
key ? key : "-");
switch(info->genlhdr->cmd) {
case DRBD_RESOURCE_STATE:
if (action != NOTIFY_DESTROY) {
struct {
struct resource_info i;
struct resource_statistics s;
} *old, new;
if (resource_info_from_attrs(&new.i, info)) {
dbg(1, "resource info missing\n");
goto nl_out;
}
old = update_info(&key, &new, sizeof(new));
if (!old || new.i.res_role != old->i.res_role)
printf(" role:%s%s%s",
ROLE_COLOR_STRING(new.i.res_role, 1));
if (!old ||
new.i.res_susp != old->i.res_susp ||
new.i.res_susp_nod != old->i.res_susp_nod ||
new.i.res_susp_fen != old->i.res_susp_fen ||
new.i.res_susp_quorum != old->i.res_susp_quorum)
printf(" suspended:%s",
susp_str(&new.i));
if (opt_statistics) {
if (resource_statistics_from_attrs(&new.s, info)) {
dbg(1, "resource statistics missing\n");
if (old)
new.s = old->s;
} else
print_resource_statistics(0, old ? &old->s : NULL,
&new.s, nowrap_printf);
}
free(old);
} else
update_info(&key, NULL, 0);
break;
case DRBD_DEVICE_STATE:
if (action != NOTIFY_DESTROY) {
struct {
struct device_info i;
struct device_statistics s;
} *old, new;
new.i.is_intentional_diskless = IS_INTENTIONAL_DEF;
if (device_info_from_attrs(&new.i, info)) {
dbg(1, "device info missing\n");
goto nl_out;
}
old = update_info(&key, &new, sizeof(new));
if (!old || new.i.dev_disk_state != old->i.dev_disk_state) {
bool intentional = new.i.is_intentional_diskless == 1;
printf(" disk:%s%s%s",
DISK_COLOR_STRING(new.i.dev_disk_state, intentional, true));
printf(" client:%s", intentional_diskless_str(&new.i));
}
if (opt_statistics) {
if (device_statistics_from_attrs(&new.s, info)) {
dbg(1, "device statistics missing\n");
if (old)
new.s = old->s;
} else
print_device_statistics(0, old ? &old->s : NULL,
&new.s, nowrap_printf);
}
free(old);
} else
update_info(&key, NULL, 0);
break;
case DRBD_CONNECTION_STATE:
if (action != NOTIFY_DESTROY) {
struct {
struct connection_info i;
struct connection_statistics s;
} *old, new;
if (connection_info_from_attrs(&new.i, info)) {
dbg(1, "connection info missing\n");
goto nl_out;
}
old = update_info(&key, &new, sizeof(new));
if (!old ||
new.i.conn_connection_state != old->i.conn_connection_state)
printf(" connection:%s%s%s",
CONN_COLOR_STRING(new.i.conn_connection_state));
if (!old ||
new.i.conn_role != old->i.conn_role)
printf(" role:%s%s%s",
ROLE_COLOR_STRING(new.i.conn_role, 0));
if (opt_statistics) {
if (connection_statistics_from_attrs(&new.s, info)) {
dbg(1, "connection statistics missing\n");
if (old)
new.s = old->s;
} else
print_connection_statistics(0, old ? &old->s : NULL,
&new.s, nowrap_printf);
}
free(old);
} else
update_info(&key, NULL, 0);
break;
case DRBD_PEER_DEVICE_STATE:
if (action != NOTIFY_DESTROY) {
struct {
struct peer_device_info i;
struct peer_device_statistics s;
} *old, new;
new.i.peer_is_intentional_diskless = IS_INTENTIONAL_DEF;
if (peer_device_info_from_attrs(&new.i, info)) {
dbg(1, "peer device info missing\n");
goto nl_out;
}
old = update_info(&key, &new, sizeof(new));
if (!old || new.i.peer_repl_state != old->i.peer_repl_state)
printf(" replication:%s%s%s",
REPL_COLOR_STRING(new.i.peer_repl_state));
if (!old || new.i.peer_disk_state != old->i.peer_disk_state) {
bool intentional = new.i.peer_is_intentional_diskless == 1;
printf(" peer-disk:%s%s%s",
DISK_COLOR_STRING(new.i.peer_disk_state, intentional, false));
printf(" peer-client:%s", peer_intentional_diskless_str(&new.i));
}
if (!old ||
new.i.peer_resync_susp_user != old->i.peer_resync_susp_user ||
new.i.peer_resync_susp_peer != old->i.peer_resync_susp_peer ||
new.i.peer_resync_susp_dependency != old->i.peer_resync_susp_dependency)
printf(" resync-suspended:%s",
resync_susp_str(&new.i));
if (opt_statistics) {
if (peer_device_statistics_from_attrs(&new.s, info)) {
dbg(1, "peer device statistics missing\n");
if (old)
new.s = old->s;
} else
print_peer_device_statistics(0, old ? &old->s : NULL,
&new.s, nowrap_printf);
}
free(old);
} else
update_info(&key, NULL, 0);
break;
case DRBD_PATH_STATE:
if (action != NOTIFY_DESTROY) {
struct drbd_path_info new = {}, *old;
if (drbd_path_info_from_attrs(&new, info)) {
dbg(1, "path info missing\n");
goto nl_out;
}
old = update_info(&key, &new, sizeof(new));
if (!old || old->path_established != new.path_established)
printf(" established:%s",
new.path_established ? "yes" : "no");
free(old);
} else
update_info(&key, NULL, 0);
break;
case DRBD_HELPER: {
struct drbd_helper_info helper_info;
if (!drbd_helper_info_from_attrs(&helper_info, info)) {
printf(" helper:%s", helper_info.helper_name);
if (action == NOTIFY_RESPONSE)
printf(" status:%u", helper_info.helper_status);
} else {
dbg(1, "helper info missing\n");
goto nl_out;
}
}
break;
case DRBD_INITIAL_STATE_DONE:
break;
}
nl_out:
printf("\n");
out:
free(key);
fflush(stdout);
if (opt_now && info->genlhdr->cmd == DRBD_INITIAL_STATE_DONE)
return -1;
return 0;
fail:
perror(progname);
exit(20);
}
void peer_devices_append(struct peer_devices_list *peer_devices, struct genl_info *info)
{
struct peer_devices_list *peer_device, **tail;
if (!peer_devices)
return;
for (peer_device = peer_devices; peer_device; peer_device = peer_device->next)
tail = &peer_device->next;
remember_peer_device(NULL, info, &tail);
}
/* Actually waits for all volumes of a connection... */
static int wait_for_family(struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
struct peer_devices_list *peer_devices = u_ptr;
struct drbd_cfg_context ctx = { .ctx_volume = -1U, .ctx_peer_node_id = -1U };
struct drbd_notification_header nh = { .nh_type = -1U };
struct drbd_genlmsghdr *dh;
if (!info)
return 0;
if (drbd_cfg_context_from_attrs(&ctx, info) ||
drbd_notification_header_from_attrs(&nh, info))
return 0;
dh = info->userhdr;
if (dh->ret_code != NO_ERROR)
return dh->ret_code;
if ((nh.nh_type & ~NOTIFY_FLAGS) == NOTIFY_DESTROY)
return 0;
switch(info->genlhdr->cmd) {
case DRBD_CONNECTION_STATE: {
struct connection_info connection_info;
if ((nh.nh_type & ~NOTIFY_FLAGS) == NOTIFY_CREATE)
break; /* Ignore C_STANDALONE while creating it */
if (connection_info_from_attrs(&connection_info, info)) {
dbg(1, "connection info missing\n");
break;
}
if (connection_info.conn_connection_state < C_UNCONNECTED) {
if (!wait_after_split_brain)
return -1; /* done waiting */
fprintf(stderr, "\ndrbd %s connection to peer-id %u ('%s') is %s, "
"but I'm configured to wait anways (--wait-after-sb)\n",
ctx.ctx_resource_name, ctx.ctx_peer_node_id, ctx.ctx_conn_name,
drbd_conn_str(connection_info.conn_connection_state));
}
break;
}
case DRBD_PEER_DEVICE_STATE: {
struct peer_device_info peer_device_info;
struct peer_devices_list *peer_device;
int nr_peer_devices = 0, nr_done = 0;
bool wait_connect;
if (peer_device_info_from_attrs(&peer_device_info, info)) {
dbg(1, "peer device info missing\n");
break;
}
wait_connect = strstr(cm->cmd, "sync") == NULL;
if ((nh.nh_type & ~NOTIFY_FLAGS) == NOTIFY_CREATE)
peer_devices_append(peer_devices, info);
for (peer_device = peer_devices;
peer_device;
peer_device = peer_device->next) {
enum drbd_repl_state rs;
if (peer_device_ctx_match(&ctx, &peer_device->ctx))
peer_device->info = peer_device_info;
/* wait-*-volume: filter out all but the specific peer device */
if (cm->ctx_key == CTX_PEER_DEVICE &&
!peer_device_ctx_match(&global_ctx, &peer_device->ctx))
continue;
/* wait-*-connection: filter out other connections */
if (cm->ctx_key == CTX_PEER_NODE &&
peer_device->ctx.ctx_peer_node_id != global_ctx.ctx_peer_node_id)
continue;
/* wait-*-resource: no filter */
nr_peer_devices++;
rs = peer_device->info.peer_repl_state;
if (rs == L_ESTABLISHED ||
(wait_connect && rs > L_ESTABLISHED) ||
peer_device->timeout_ms == 0)
nr_done++;
}
if (nr_peer_devices == nr_done)
return -1; /* Done with waiting */
break;
}
}
return 0;
}
/*
* Check if an integer is a power of two.
*/
static bool power_of_two(int i)
{
return i && !(i & (i - 1));
}
static void print_command_usage(struct drbd_cmd *cm, enum usage_type ut)
{
struct drbd_argument *args;
if(ut == XML) {
printf("<command name=\"%s\">\n", cm->cmd);
if (cm->summary)
printf("\t<summary>%s</summary>\n", cm->summary);
if (cm->ctx_key && ut != BRIEF) {
enum cfg_ctx_key ctx = cm->ctx_key, arg;
bool more_than_one_choice =
!power_of_two(ctx & ~CTX_MULTIPLE_ARGUMENTS) &&
!(ctx & CTX_MULTIPLE_ARGUMENTS);
const char *indent = "\t\t" + !more_than_one_choice;
if (more_than_one_choice)
printf("\t<group>\n");
ctx |= CTX_MULTIPLE_ARGUMENTS;
for (arg = ctx_next_arg(&ctx); arg; arg = ctx_next_arg(&ctx))
printf("%s<argument>%s</argument>\n",
indent, ctx_arg_string(arg, ut));
if (more_than_one_choice)
printf("\t</group>\n");
}
if(cm->drbd_args) {
for (args = cm->drbd_args; args->name; args++) {
printf("\t<argument>%s</argument>\n",
args->name);
}
}
if (cm->options) {
struct option *option;
for (option = cm->options; option->name; option++) {
/*
* The "string" options here really are
* timeouts, but we can't describe them
* in a resonable way here.
*/
printf("\t<option name=\"%s\" type=\"%s\">\n"
"\t</option>\n",
option->name,
option->has_arg == no_argument ?
"flag" : "string");
}
}
if (cm->set_defaults)
printf("\t<option name=\"set-defaults\" type=\"flag\">\n"
"\t</option>\n");
if (cm->ctx) {
struct field_def *field;
for (field = cm->ctx->fields; field->name; field++)
field->ops->describe_xml(field);
}
printf("</command>\n");
return;
}
if (ut == BRIEF) {
wrap_printf(4, "%s - ", cm->cmd);
if (cm->summary)
wrap_printf_wordwise(8, cm->summary);
wrap_printf(4, "\n");
} else {
wrap_printf(0, "%s %s", progname, cm->cmd);
if (cm->summary)
wrap_printf(4, " - %s", cm->summary);
wrap_printf(4, "\n\n");
wrap_printf(0, "USAGE: %s %s", progname, cm->cmd);
if (cm->ctx_key && ut != BRIEF) {
enum cfg_ctx_key ctx = cm->ctx_key, arg;
bool more_than_one_choice =
!power_of_two(ctx & ~CTX_MULTIPLE_ARGUMENTS) &&
!(ctx & CTX_MULTIPLE_ARGUMENTS);
bool first = true;
if (more_than_one_choice)
wrap_printf(4, " {");
ctx |= CTX_MULTIPLE_ARGUMENTS;
for (arg = ctx_next_arg(&ctx); arg; arg = ctx_next_arg(&ctx)) {
if (more_than_one_choice && !first)
wrap_printf(4, " |");
first = false;
wrap_printf(4, " %s", ctx_arg_string(arg, ut));
}
if (more_than_one_choice)
wrap_printf(4, " }");
}
if (cm->drbd_args) {
for (args = cm->drbd_args; args->name; args++)
wrap_printf(4, " {%s}", args->name);
}
if (cm->options || cm->set_defaults || cm->ctx)
wrap_printf(4, "\n");
if (cm->options) {
struct option *option;
for (option = cm->options; option->name; option++)
wrap_printf(4, " [--%s%s]",
option->name,
option->has_arg == no_argument ?
"" : "=...");
}
if (cm->set_defaults)
wrap_printf(4, " [--set-defaults]");
if (cm->ctx) {
struct field_def *field;
for (field = cm->ctx->fields; field->name; field++) {
char buffer[300];
int n;
n = field->ops->usage(field, buffer, sizeof(buffer));
assert(n < sizeof(buffer));
wrap_printf(4, " %s", buffer);
}
}
wrap_printf(4, "\n");
}
}
static void print_usage_and_exit(const char *addinfo)
{
size_t i;
printf("drbdsetup - Configure the DRBD kernel module.\n\n"
"USAGE: %s command {arguments} [options]\n"
"\nCommands:\n",cmdname);
for (i = 0; i < ARRAY_SIZE(commands); i++)
print_command_usage(&commands[i], BRIEF);
printf("\nUse 'drbdsetup help command' for command-specific help.\n\n");
if (addinfo) /* FIXME: ?! */
printf("\n%s\n", addinfo);
exit(20);
}
static int modprobe_drbd(void)
{
struct stat sb;
int ret, retries = 10;
ret = stat("/proc/drbd", &sb);
if (ret && errno == ENOENT) {
ret = system("/sbin/modprobe drbd");
if (ret != 0) {
fprintf(stderr, "Failed to modprobe drbd (%m)\n");
return 0;
}
for(;;) {
struct timespec ts = {
.tv_nsec = 1000000,
};
ret = stat("/proc/drbd", &sb);
if (!ret || retries-- == 0)
break;
nanosleep(&ts, NULL);
}
}
if (ret) {
fprintf(stderr, "Could not stat /proc/drbd: %m\n");
fprintf(stderr, "Make sure that the DRBD kernel module is installed "
"and can be loaded!\n");
}
return ret == 0;
}
static void maybe_exec_legacy_drbdsetup(char **argv)
{
const struct version *driver_version = drbd_driver_version(FALLBACK_TO_UTILS);
if (driver_version->version.major == 8 &&
driver_version->version.minor == 3) {
#ifdef DRBD_LEGACY_83
static const char * const drbdsetup_83 = "drbdsetup-83";
add_lib_drbd_to_path();
execvp(drbdsetup_83, argv);
fprintf(stderr, "execvp() failed to exec %s: %m\n", drbdsetup_83);
#else
config_help_legacy("drbdsetup", driver_version);
#endif
exit(20);
}
if (driver_version->version.major == 8 &&
driver_version->version.minor == 4) {
#ifdef DRBD_LEGACY_84
static const char * const drbdsetup_84 = "drbdsetup-84";
add_lib_drbd_to_path();
execvp(drbdsetup_84, argv);
fprintf(stderr, "execvp() failed to exec %s: %m\n", drbdsetup_84);
#else
config_help_legacy("drbdsetup", driver_version);
#endif
exit(20);
}
}
int main(int argc, char **argv)
{
struct drbd_cmd *cmd;
struct option *options;
const char *opts;
int c, rv = 0;
int longindex, first_optind;
progname = basename(argv[0]);
if (chdir("/")) {
/* highly unlikely, but gcc is picky */
perror("cannot chdir /");
return -111;
}
cmdname = strrchr(argv[0],'/');
if (cmdname)
argv[0] = ++cmdname;
else
cmdname = argv[0];
if (argc > 2 && (!strcmp(argv[2], "--help") || !strcmp(argv[2], "-h"))) {
char *swap = argv[1];
argv[1] = argv[2];
argv[2] = swap;
}
if (argc > 1 && (!strcmp(argv[1], "help") || !strcmp(argv[1], "xml-help") ||
!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
enum usage_type usage_type = !strcmp(argv[1], "xml-help") ? XML : FULL;
if(argc > 2) {
cmd = find_cmd_by_name(argv[2]);
if(cmd) {
print_command_usage(cmd, usage_type);
exit(0);
} else
print_usage_and_exit("unknown command");
} else
print_usage_and_exit(NULL);
}
/*
* drbdsetup previously took the object to operate on as its first argument,
* followed by the command. For backwards compatibility, still support his.
*/
if (argc >= 3 && !find_cmd_by_name(argv[1]) && find_cmd_by_name(argv[2])) {
char *swap = argv[1];
argv[1] = argv[2];
argv[2] = swap;
}
if (argc < 2)
print_usage_and_exit(NULL);
if (!modprobe_drbd()) {
if (!strcmp(argv[1], "down") ||
!strcmp(argv[1], "secondary") ||
!strcmp(argv[1], "disconnect") ||
!strcmp(argv[1], "detach"))
return 0; /* "down" succeeds even if drbd is missing */
return 20;
}
maybe_exec_legacy_drbdsetup(argv);
cmd = find_cmd_by_name(argv[1]);
if (!cmd)
print_usage_and_exit("invalid command");
/* Make argv[0] the command name so that getopt_long() will leave it in
* the first position. */
argv++;
argc--;
options = make_longoptions(cmd);
opts = make_optstring(options);
for (;;) {
c = getopt_long(argc, argv, opts, options, &longindex);
if (c == -1)
break;
if (c == '?' || c == ':')
print_usage_and_exit(NULL);
}
/* All non-option arguments now are in argv[optind .. argc - 1]. */
first_optind = optind;
if (cmd->continuous_poll && kernel_older_than(2, 6, 23)) {
/* with newer kernels, we need to use setsockopt NETLINK_ADD_MEMBERSHIP */
/* maybe more specific: (1 << GENL_ID_CTRL)? */
drbd_genl_family.nl_groups = -1;
}
drbd_sock = genl_connect_to_family(&drbd_genl_family);
if (!drbd_sock) {
fprintf(stderr, "Could not connect to 'drbd' generic netlink family\n");
return 20;
}
if (drbd_genl_family.version != GENL_MAGIC_VERSION ||
drbd_genl_family.hdrsize != sizeof(struct drbd_genlmsghdr)) {
fprintf(stderr, "API mismatch!\n\t"
"API version drbdsetup: %u kernel: %u\n\t"
"header size drbdsetup: %u kernel: %u\n",
GENL_MAGIC_VERSION, drbd_genl_family.version,
(unsigned)sizeof(struct drbd_genlmsghdr),
drbd_genl_family.hdrsize);
return 20;
}
context = 0;
enum cfg_ctx_key ctx_key = cmd->ctx_key, next_arg;
for (next_arg = ctx_next_arg(&ctx_key);
next_arg;
next_arg = ctx_next_arg(&ctx_key), optind++) {
if (argc == optind &&
!(ctx_key & CTX_MULTIPLE_ARGUMENTS) && (next_arg & CTX_ALL)) {
context |= CTX_ALL; /* assume "all" if no argument is given */
objname = "all";
break;
} else if (argc <= optind) {
fprintf(stderr, "Missing argument %d to command\n", optind);
print_command_usage(cmd, FULL);
exit(20);
} else if (next_arg & (CTX_RESOURCE | CTX_MINOR | CTX_ALL)) {
ensure_sanity_of_res_name(argv[optind]);
if (!objname)
objname = argv[optind];
if (!strcmp(argv[optind], "all")) {
if (!(next_arg & CTX_ALL))
print_usage_and_exit("command does not accept argument 'all'");
context |= CTX_ALL;
} else if (next_arg & CTX_MINOR) {
minor = dt_minor_of_dev(argv[optind]);
if (minor == -1U && next_arg == CTX_MINOR) {
fprintf(stderr, "Cannot determine minor device number of "
"device '%s'\n",
argv[optind]);
exit(20);
}
context |= CTX_MINOR;
} else /* not "all", and not a minor number/device name */ {
if (!(next_arg & CTX_RESOURCE)) {
fprintf(stderr, "command does not accept argument '%s'\n",
objname);
print_command_usage(cmd, FULL);
exit(20);
}
context |= CTX_RESOURCE;
assert(strlen(objname) < sizeof(global_ctx.ctx_resource_name));
memset(global_ctx.ctx_resource_name, 0, sizeof(global_ctx.ctx_resource_name));
global_ctx.ctx_resource_name_len = strlen(objname);
strcpy(global_ctx.ctx_resource_name, objname);
}
} else {
if (next_arg == CTX_MY_ADDR) {
const char *str = argv[optind];
struct sockaddr_storage *x;
if (strncmp(str, "local:", 6) == 0)
str += 6;
assert(sizeof(global_ctx.ctx_my_addr) >= sizeof(*x));
x = (struct sockaddr_storage *)&global_ctx.ctx_my_addr;
global_ctx.ctx_my_addr_len = sockaddr_from_str(x, str);
} else if (next_arg == CTX_PEER_ADDR) {
const char *str = argv[optind];
struct sockaddr_storage *x;
if (strncmp(str, "peer:", 5) == 0)
str += 5;
assert(sizeof(global_ctx.ctx_peer_addr) >= sizeof(*x));
x = (struct sockaddr_storage *)&global_ctx.ctx_peer_addr;
global_ctx.ctx_peer_addr_len = sockaddr_from_str(x, str);
} else if (next_arg == CTX_VOLUME) {
global_ctx.ctx_volume = m_strtoll(argv[optind], 1);
} else if (next_arg == CTX_PEER_NODE_ID) {
global_ctx.ctx_peer_node_id = m_strtoll(argv[optind], 1);
} else
assert(0);
context |= next_arg;
}
}
/* Remove the options we have already processed from argv */
if (first_optind != optind) {
int n;
for (n = 0; n < argc - optind; n++)
argv[first_optind + n] = argv[optind + n];
argc -= optind - first_optind;
}
if (!objname)
objname = "??";
if ((context & CTX_MINOR) && !cmd->lockless)
lock_fd = dt_lock_drbd(minor);
rv = cmd->function(cmd, argc, argv);
if ((context & CTX_MINOR) && !cmd->lockless)
dt_unlock_drbd(lock_fd);
return rv;
}
#endif