/*
 * 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 <libgen.h>
#include <time.h>
#include <search.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

/*
 * 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 "linux/genl_magic_func.h"
#include "drbdtool_common.h"
#include "registry.h"
#include "config.h"
#include "config_flags.h"
#include "wrap_printf.h"
#include "drbdsetup_colors.h"
#include "drbd_strings.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

/* 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_MINOR = 1,
	CTX_RESOURCE = 2,
	CTX_ALL = 4,
	CTX_CONNECTION = 8,

	CTX_RESOURCE_AND_CONNECTION = 16,
};

struct drbd_cmd {
	const char* cmd;
	const enum cfg_ctx_key ctx_key;
	const int cmd_id;
	const int tla_id; /* top level attribute id */
	int (*function)(const struct drbd_cmd *, int, char **);
	struct drbd_argument *drbd_args;
	int (*show_function)(const struct drbd_cmd*, struct genl_info *, void *u_prt);
	struct option *options;
	bool missing_ok;
	bool warn_on_missing;
	bool continuous_poll;
	bool wait_for_connect_timeouts;
	bool set_defaults;
	bool lockless;
	struct context_def *ctx;
};

// other functions
static void print_command_usage(const struct drbd_cmd *cm, enum usage_type);

// command functions
static int generic_config_cmd(const struct drbd_cmd *cm, int argc, char **argv);
static int down_cmd(const struct drbd_cmd *cm, int argc, char **argv);
static int generic_get_cmd(const struct drbd_cmd *cm, int argc, char **argv);
static int del_minor_cmd(const struct drbd_cmd *cm, int argc, char **argv);
static int del_resource_cmd(const struct drbd_cmd *cm, int argc, char **argv);
static int status_cmd(const struct drbd_cmd *cm, int argc, char **argv);

// sub commands for generic_get_cmd
static int print_notifications(const struct drbd_cmd *, struct genl_info *, void *u_ptr);
static int show_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int role_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int sh_status_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int cstate_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int dstate_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int uuids_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int lk_bdev_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr);
static int print_broadcast_events(const struct drbd_cmd *, struct genl_info *, void *u_ptr);
static int w_connected_state(const struct drbd_cmd *, struct genl_info *, void *u_ptr);
static int w_synced_state(const struct drbd_cmd *, struct genl_info *, void *u_ptr);

// 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_resource_name(struct drbd_argument *ad, struct msg_buff *msg, struct drbd_genlmsghdr *dhdr, char* arg);
static int conv_volume(struct drbd_argument *ad, struct msg_buff *msg, struct drbd_genlmsghdr *dhdr, char* arg);
static int conv_minor(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 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 *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 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'},
	{ 0,            0,           0,  0  }
};

struct option events_cmd_options[] = {
	{ "timestamps", no_argument, 0, 'T' },
	{ "statistics", no_argument, 0, 's' },
	{ "now", no_argument, 0, 'n' },
	{ }
};

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' },
	{ }
};

#define F_CONFIG_CMD	generic_config_cmd
#define NO_PAYLOAD	0
#define F_GET_CMD(scmd)	DRBD_ADM_GET_STATUS, NO_PAYLOAD, generic_get_cmd, \
			.show_function = scmd
#define F_NEW_EVENTS_CMD(scmd)	DRBD_ADM_GET_INITIAL_STATE, NO_PAYLOAD, generic_get_cmd, \
			.show_function = scmd

const struct drbd_cmd commands[] = {
	{"primary", CTX_MINOR, DRBD_ADM_PRIMARY, DRBD_NLA_SET_ROLE_PARMS,
		F_CONFIG_CMD,
	 .ctx = &primary_cmd_ctx },

	{"secondary", CTX_MINOR, DRBD_ADM_SECONDARY, NO_PAYLOAD, F_CONFIG_CMD },

	{"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 },

	{"disk-options", CTX_MINOR, DRBD_ADM_CHG_DISK_OPTS, DRBD_NLA_DISK_CONF,
		F_CONFIG_CMD,
	 .set_defaults = true,
	 .ctx = &disk_options_ctx },

	{"detach", CTX_MINOR, DRBD_ADM_DETACH, DRBD_NLA_DETACH_PARMS, F_CONFIG_CMD,
	 .ctx = &detach_cmd_ctx },

	{"connect", CTX_RESOURCE_AND_CONNECTION, DRBD_ADM_CONNECT, DRBD_NLA_NET_CONF,
		F_CONFIG_CMD,
	 .ctx = &connect_cmd_ctx },

	{"net-options", CTX_CONNECTION, DRBD_ADM_CHG_NET_OPTS, DRBD_NLA_NET_CONF,
		F_CONFIG_CMD,
	 .set_defaults = true,
	 .ctx = &net_options_ctx },

	{"disconnect", CTX_CONNECTION, DRBD_ADM_DISCONNECT, DRBD_NLA_DISCONNECT_PARMS,
		F_CONFIG_CMD,
	 .ctx = &disconnect_cmd_ctx },

	{"resize", CTX_MINOR, DRBD_ADM_RESIZE, DRBD_NLA_RESIZE_PARMS,
		F_CONFIG_CMD,
	 .ctx = &resize_cmd_ctx },

	{"resource-options", CTX_RESOURCE, DRBD_ADM_RESOURCE_OPTS, DRBD_NLA_RESOURCE_OPTS,
		F_CONFIG_CMD,
	 .set_defaults = true,
	 .ctx = &resource_options_cmd_ctx },

	{"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 },

	{"invalidate", CTX_MINOR, DRBD_ADM_INVALIDATE, NO_PAYLOAD, F_CONFIG_CMD, },
	{"invalidate-remote", CTX_MINOR, DRBD_ADM_INVAL_PEER, NO_PAYLOAD, F_CONFIG_CMD, },
	{"pause-sync", CTX_MINOR, DRBD_ADM_PAUSE_SYNC, NO_PAYLOAD, F_CONFIG_CMD, },
	{"resume-sync", CTX_MINOR, DRBD_ADM_RESUME_SYNC, NO_PAYLOAD, F_CONFIG_CMD, },
	{"suspend-io", CTX_MINOR, DRBD_ADM_SUSPEND_IO, NO_PAYLOAD, F_CONFIG_CMD, },
	{"resume-io", CTX_MINOR, DRBD_ADM_RESUME_IO, NO_PAYLOAD, F_CONFIG_CMD, },
	{"outdate", CTX_MINOR, DRBD_ADM_OUTDATE, NO_PAYLOAD, F_CONFIG_CMD, },
	{"verify", CTX_MINOR, DRBD_ADM_START_OV, DRBD_NLA_START_OV_PARMS,
		F_CONFIG_CMD,
	 .ctx = &verify_cmd_ctx },
	{"down", CTX_RESOURCE, DRBD_ADM_DOWN, NO_PAYLOAD, down_cmd,
		.missing_ok = true,
		.warn_on_missing = true, },
	{"state", CTX_MINOR, F_GET_CMD(role_scmd) },
	{"role", CTX_MINOR, F_GET_CMD(role_scmd),
		.lockless = true, },
	{"sh-status", CTX_MINOR | CTX_RESOURCE | CTX_ALL,
		F_GET_CMD(sh_status_scmd),
		.missing_ok = true,
		.lockless = true, },
	{"cstate", CTX_MINOR, F_GET_CMD(cstate_scmd),
		.lockless = true, },
	{"dstate", CTX_MINOR, F_GET_CMD(dstate_scmd),
		.lockless = true, },
	{"show-gi", CTX_MINOR, F_GET_CMD(uuids_scmd),
		.lockless = true, },
	{"get-gi", CTX_MINOR, F_GET_CMD(uuids_scmd),
		.lockless = true, },
	{"show", CTX_MINOR | CTX_RESOURCE | CTX_ALL, F_GET_CMD(show_scmd),
		.options = show_cmd_options,
		.lockless = true, },
	{"status", CTX_RESOURCE | CTX_ALL, 0, 0, status_cmd,
		.options = status_cmd_options,
		.lockless = true, },
	{"check-resize", CTX_MINOR, F_GET_CMD(lk_bdev_scmd),
		.lockless = true, },
	{"events", CTX_MINOR | CTX_RESOURCE | CTX_ALL, F_GET_CMD(print_broadcast_events),
		.missing_ok = true,
		.continuous_poll = true,
		.lockless = true, },
	{"events2", CTX_RESOURCE | CTX_ALL, F_NEW_EVENTS_CMD(print_notifications),
		.options = events_cmd_options,
		.missing_ok = true,
		.continuous_poll = true,
		.lockless = true },
	{"wait-connect", CTX_MINOR, F_GET_CMD(w_connected_state),
		.options = wait_cmds_options,
		.continuous_poll = true,
		.wait_for_connect_timeouts = true,
		.lockless = true, },
	{"wait-sync", CTX_MINOR, F_GET_CMD(w_synced_state),
		.options = wait_cmds_options,
		.continuous_poll = true,
		.wait_for_connect_timeouts = true,
		.lockless = true, },

	{"new-resource", CTX_RESOURCE, DRBD_ADM_NEW_RESOURCE, DRBD_NLA_RESOURCE_OPTS, F_CONFIG_CMD,
	 .ctx = &resource_options_cmd_ctx },

	/* only payload is resource name and volume number */
	{"new-minor", 0, DRBD_ADM_NEW_MINOR, DRBD_NLA_CFG_CONTEXT,
		F_CONFIG_CMD,
	 .drbd_args = (struct drbd_argument[]) {
		 { "resource", T_ctx_resource_name, conv_resource_name },
		 { "minor", 0, conv_minor },
		 { "volume", T_ctx_volume, conv_volume },
		 { } },
	 .ctx = &new_minor_cmd_ctx },

	{"del-minor", CTX_MINOR, DRBD_ADM_DEL_MINOR, NO_PAYLOAD, del_minor_cmd, },
	{"del-resource", CTX_RESOURCE, DRBD_ADM_DEL_RESOURCE, NO_PAYLOAD, del_resource_cmd, }
};

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",
};
#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;
enum cfg_ctx_key context;
char *opt_local_addr, *opt_peer_addr;

int lock_fd;

struct genl_sock *drbd_sock = NULL;
int try_genl = 1;

struct genl_family drbd_genl_family = {
	.name = "drbd",
	.version = GENL_MAGIC_VERSION,
	.hdrsize = GENL_MAGIC_FAMILY_HDRSZ,
};

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);
}

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_resource_name(struct drbd_argument *ad, struct msg_buff *msg,
			      struct drbd_genlmsghdr *dhdr, char* arg)
{
	/* additional sanity checks? */
	nla_put_string(msg, T_ctx_resource_name, arg);
	return NO_ERROR;
}

static int conv_volume(struct drbd_argument *ad, struct msg_buff *msg,
		       struct drbd_genlmsghdr *dhdr, char* arg)
{
	unsigned vol = m_strtoll(arg,1);
	/* sanity check on vol < 256? */
	nla_put_u32(msg, T_ctx_volume, vol);
	return NO_ERROR;
}

static int conv_minor(struct drbd_argument *ad, struct msg_buff *msg,
		      struct drbd_genlmsghdr *dhdr, char* arg)
{
	unsigned minor = dt_minor_of_dev(arg);
	if (minor == -1U) {
		fprintf(stderr, "Cannot determine minor device number of "
				"device '%s'\n",
			arg);
		return OTHER_ERROR;
	}
	dhdr->minor = minor;
	return NO_ERROR;
}

static struct option *make_longoptions(const 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 print_config_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(const struct drbd_cmd *cm, int argc,
			       char **argv, int quiet)
{
	struct drbd_argument *ad = cm->drbd_args;
	struct nlattr *nla;
	struct option *options;
	int c, i;
	int rv = NO_ERROR;
	char *desc = NULL; /* error description from kernel reply message */

	struct drbd_genlmsghdr *dhdr;
	struct msg_buff *smsg;
	struct iovec iov;

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

	dhdr = genlmsg_put(smsg, &drbd_genl_family, 0, cm->cmd_id);
	dhdr->minor = -1;
	dhdr->flags = 0;

	i = 1;
	if (context & (CTX_RESOURCE | CTX_CONNECTION)) {
		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_CONNECTION) {
			nla_put_address(smsg, T_ctx_my_addr, opt_local_addr);
			nla_put_address(smsg, T_ctx_peer_addr, opt_peer_addr);
		}
		nla_nest_end(smsg, nla);
	} else if (context & CTX_MINOR) {
		dhdr->minor = minor;
		i++;
	}

	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->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;

	/* 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 (rv == NO_ERROR) {
		int received;

		if (nla)
			nla_nest_end(smsg, nla);
		if (genl_send(drbd_sock, smsg)) {
			desc = "error sending config command";
			rv = OTHER_ERROR;
			goto error;
		}

retry_recv:
		/* reduce timeout! limit retries */
		received = genl_recv_msgs(drbd_sock, &iov, &desc, 120000);
		if (received > 0) {
			struct nlmsghdr *nlh = (struct nlmsghdr*)iov.iov_base;
			struct drbd_genlmsghdr *dh = genlmsg_data(nlmsg_data(nlh));
			ASSERT(dh->minor == minor);
			rv = dh->ret_code;

			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);
		} else {
			if (received == -E_RCV_ERROR_REPLY && !errno)
					goto retry_recv;
			if (!desc)
				desc = "error receiving config reply";

			rv = OTHER_ERROR;
		}
	}
error:
	msg_free(smsg);

	if (!quiet)
		rv = print_config_error(rv, desc);
	free(iov.iov_base);
	return rv;
}

static int generic_config_cmd(const struct drbd_cmd *cm, int argc, char **argv)
{
	return _generic_config_cmd(cm, argc, argv, 0);
}

static int del_minor_cmd(const 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(const 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 const 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(const char *cmd_name, const char *sect_name)
{
	const struct drbd_cmd *cmd;
	struct field_def *field;
	int opened = 0;

	cmd = find_cmd_by_name(cmd_name);
	if (!cmd) {
		fprintf(stderr, "%s internal error, no such cmd %s\n",
				cmdname, cmd_name);
		abort();
	}
	if (!global_attrs[cmd->tla_id])
		return;
	if (drbd_nla_parse_nested(nested_attr_tb, cmd->ctx->nla_policy_size - 1,
			     global_attrs[cmd->tla_id], cmd->ctx->nla_policy)) {
		fprintf(stderr, "nla_policy violation for %s payload!\n", sect_name);
		/* still, print those that validated ok */
	}

	if (!cmd->ctx)
		return;
	for (field = cmd->ctx->fields; field->name; field++) {
		struct nlattr *nlattr;
		const char *str;
		bool is_default;

		nlattr = ntb(field->nla_type);
		if (!nlattr)
			continue;
		if (!opened) {
			opened=1;
			printI("%s {\n",sect_name);
			++indent;
		}
		str = field->get(cmd->ctx, field, nlattr);
		is_default = field->is_default(field, str);
		if (is_default && !show_defaults)
			continue;
		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_timo_ctx {
	unsigned minor;
	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_timo_ctx *ctx)
{
	char *desc = NULL;
	struct drbd_genlmsghdr *dhdr;
	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 = ctx->minor;
	dhdr->flags = 0;

	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;
		ASSERT(dh->minor == ctx->minor);
		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 bool opt_now;
static bool opt_verbose;
static bool opt_statistics;
static bool opt_timestamps;

static int generic_get(const 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;

		ret = poll(pollfds, 2, timeout_arg);
		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;

			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 (context) {
					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_ALL:
						break;

					case CTX_RESOURCE:
						if (strcmp(objname, ctx.ctx_resource_name))
							continue;

						break;
#if 0
					case CTX_CONNECTION:
					case CTX_CONNECTION | CTX_RESOURCE:
						if (!endpoints_equal(&ctx, &global_ctx))
							continue;
						break;
#endif
#if 0
					case CTX_PEER_DEVICE:
						if (!endpoints_equal(&ctx, &global_ctx) ||
						    ctx.ctx_volume != global_ctx.ctx_volume)
							continue;
						break;
#endif
					default:
						assert(0);
					}
				}
			}
			rv = dh->ret_code;
			if (rv == ERR_MINOR_INVALID) {
				if (cm->warn_on_missing)
					fprintf(stderr, "Minor invalid");

				if (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 = print_config_error(rv, desc);
	free(iov.iov_base);
	return err;
}

static int generic_get_cmd(const struct drbd_cmd *cm, int argc, char **argv)
{
	static struct option no_options[] = { { } };
	struct choose_timo_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 timeout_ms = -1;  /* "infinite" */
	struct option *options = cm->options ? cm->options : no_options;
	const char *opts = make_optstring(options);

	optind = 0;  /* reset getopt_long() */
	for(;;) {
		int 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 '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;
		}
	}
	if (optind < argc) {
		warn_print_excess_args(argc, argv, optind);
		return 20;
	}

	if (cm->wait_for_connect_timeouts) {
		/* wait-connect, wait-sync */
		struct msg_buff *smsg;
		struct iovec iov;
		int rr;

		iov.iov_len = 8192;
		iov.iov_base = malloc(iov.iov_len);
		smsg = msg_new(DEFAULT_MSG_SIZE);
		if (!smsg || !iov.iov_base) {
			fprintf(stderr, "could not allocate netlink messages\n");
			return 20;
		}

		timeo_ctx.minor = minor;
		timeo_ctx.smsg = smsg;
		timeo_ctx.iov = &iov;
		rr = choose_timeout(&timeo_ctx);
		if (rr)
			return rr;
		if (timeo_ctx.timeout)
			timeout_ms = timeo_ctx.timeout * 1000;

		msg_free(smsg);
		free(iov.iov_base);
	} else if (!cm->continuous_poll)
		/* normal "get" request, or "show" */
		timeout_ms = 120000;
	/* else: events command, defaults to "infinity" */

	return generic_get(cm, timeout_ms, NULL);
}

static void show_address(void* address, int addr_len)
{
	char buffer[ADDRESS_STR_MAX];
	sprint_address(buffer, address, addr_len);
	printI("address\t\t\t%s;\n", buffer);
}

struct minors_list {
	struct minors_list *next;
	unsigned minor;
};
struct minors_list *__remembered_minors;

static int remember_minor(const struct drbd_cmd *cmd, struct genl_info *info, void *u_ptr)
{
	struct drbd_cfg_context cfg = { .ctx_volume = -1U };

	if (!info)
		return 0;

	drbd_cfg_context_from_attrs(&cfg, info);
	if (cfg.ctx_volume != -1U) {
		unsigned minor = ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
		struct minors_list *m = malloc(sizeof(*m));
		m->next = __remembered_minors;
		m->minor = minor;
		__remembered_minors = m;
	}
	return 0;
}

static void free_minors(struct minors_list *minors)
{
	while (minors) {
		struct minors_list *m = minors;
		minors = minors->next;
		free(m);
	}
}

/*
 * Expects objname to be set to the resource name or "all".
 */
static struct minors_list *enumerate_minors(void)
{
	struct drbd_cmd cmd = {
		.cmd_id = DRBD_ADM_GET_STATUS,
		.show_function = remember_minor,
		.missing_ok = true,
	};
	struct minors_list *m;
	int err;

	err = generic_get_cmd(&cmd, 0, NULL);
	m = __remembered_minors;
	__remembered_minors = NULL;
	if (err) {
		free_minors(m);
		m = NULL;
	}
	return m;
}

static int remember_resource(const 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 };
	struct resources_list *r;
	struct nlattr *res_opts = global_attrs[DRBD_NLA_RESOURCE_OPTS];

	if (!info)
		return 0;

	drbd_cfg_context_from_attrs(&cfg, info);
	r = calloc(1, sizeof(*r));

	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 err;

	objname = "all";
	minor = -1;
	err = generic_get(&cmd, 120000, &tail);
	objname = old_objname;
	minor = old_minor;
	if (err) {
		free_resources(list);
		list = NULL;
	}

	return list;
}

static int remember_device(const 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 };

	if (!info)
		return 0;

	drbd_cfg_context_from_attrs(&ctx, info);

	if (ctx.ctx_volume != -1U) {
		struct devices_list *d = calloc(1, sizeof(*d));

		d->minor =  ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
		d->ctx = ctx;
		disk_conf_from_attrs(&d->disk_conf, info);
		d->info.dev_disk_state = D_DISKLESS;
		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 err;

	objname = resource_name ? resource_name : "all";
	minor = -1;
	err = generic_get(&cmd, 120000, &tail);
	objname = old_objname;
	minor = old_minor;
	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);
	}
}

static int remember_connection(const 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 };
	struct connections_list *c;
	struct nlattr *net_conf = global_attrs[DRBD_NLA_NET_CONF];

	if (!info)
		return 0;

	drbd_cfg_context_from_attrs(&ctx, info);
	c = calloc(1, sizeof(*c));

	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);
	}
	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;
}

#if 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);
}
#endif

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;
#if 0
		qsort(array, n, sizeof(*array), (int (*)(const void *, const void *)) connection_name_cmp);
#endif
		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 err;

	objname = resource_name ? resource_name : "all";
	minor = -1;
	err = generic_get(&cmd, 120000, &tail);
	objname = old_objname;
	minor = old_minor;
	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);
	}
}

static int remember_peer_device(const 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 };
	struct peer_devices_list *p;

	if (!info)
		return 0;

	drbd_cfg_context_from_attrs(&ctx, info);
	p = calloc(1, sizeof(*p));

	if (!p)
		exit(20);

	p->ctx = ctx;
	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 err;

	objname = resource_name ? resource_name : "all";
	minor = -1;
	err = generic_get(&cmd, 120000, &tail);
	objname = old_objname;
	minor = old_minor;
	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);
	}
}

/* may be called for a "show" of a single minor device.
 * prints all available configuration information in that case.
 *
 * may also be called iteratively for a "show-all", which should try to not
 * print redundant configuration information for the same resource (tconn).
 */
static int show_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
	/* FIXME need some define for max len here */
	static char last_ctx_resource_name[128];
	static int call_count;

	struct drbd_cfg_context cfg = { .ctx_volume = -1U };
	struct disk_conf dc = { .disk_size = 0, };
	struct net_conf nc = { .timeout = 0, };;

	if (!info) {
		if (call_count) {
			--indent;
			printI("}\n"); /* close _this_host */
			--indent;
			printI("}\n"); /* close resource */
		}
		fflush(stdout);
		return 0;
	}
	call_count++;

	/* FIXME: Is the folowing check needed? */
	if (!global_attrs[DRBD_NLA_CFG_CONTEXT])
		dbg(1, "unexpected packet, configuration context missing!\n");

	drbd_cfg_context_from_attrs(&cfg, info);
	disk_conf_from_attrs(&dc, info);
	net_conf_from_attrs(&nc, info);

	if (strncmp(last_ctx_resource_name, cfg.ctx_resource_name, sizeof(last_ctx_resource_name))) {
		if (strncmp(last_ctx_resource_name, "", sizeof(last_ctx_resource_name))) {
			--indent;
			printI("}\n"); /* close _this_host */
			--indent;
			printI("}\n\n");
		}
		strncpy(last_ctx_resource_name, cfg.ctx_resource_name, sizeof(last_ctx_resource_name));

		printI("resource %s {\n", cfg.ctx_resource_name);
		++indent;
		print_options("resource-options", "options");
		print_options("net-options", "net");

		if (cfg.ctx_peer_addr_len) {
			printI("_remote_host {\n");
			++indent;
			show_address(cfg.ctx_peer_addr, cfg.ctx_peer_addr_len);
			--indent;
			printI("}\n");
		}
		printI("_this_host {\n");
		++indent;
		if (cfg.ctx_my_addr_len)
			show_address(cfg.ctx_my_addr, cfg.ctx_my_addr_len);
	}

	if (cfg.ctx_volume != -1U) {
		unsigned minor = ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
		printI("volume %d {\n", cfg.ctx_volume);
		++indent;
		printI("device\t\t\tminor %d;\n", minor);
		if (global_attrs[DRBD_NLA_DISK_CONF]) {
			if (dc.backing_dev[0]) {
				printI("disk\t\t\t\"%s\";\n", dc.backing_dev);
				printI("meta-disk\t\t\t");
				switch(dc.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(dc.meta_dev));
					break;
				default:
					printf("%s [ %d ];\n",
					       double_quote_string(dc.meta_dev),
					       dc.meta_dev_idx);
				 }
			}
		}
		print_options("attach", "disk");
		--indent;
		printI("}\n"); /* close volume */
	}

	return 0;
}

static int lk_bdev_scmd(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
	unsigned minor;
	struct disk_conf dc = { .disk_size = 0, };
	struct bdev_info bd = { 0, };
	uint64_t bd_size;
	int fd;

	if (!info)
		return 0;

	minor = ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
	disk_conf_from_attrs(&dc, info);

	if (dc.meta_dev_idx >= 0 || dc.meta_dev_idx == DRBD_MD_INDEX_FLEX_EXT) {
		lk_bdev_delete(minor);
		return 0;
	}

	fd = open(dc.backing_dev, O_RDONLY);
	if (fd == -1) {
		fprintf(stderr, "Could not open %s: %m.\n", dc.backing_dev);
		return 1;
	}
	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, dc.backing_dev))
		return 0;	/* nothing changed. */

	bd.bd_size = bd_size;
	bd.bd_name = dc.backing_dev;
	lk_bdev_save(minor, &bd);

	return 0;
}

static int sh_status_scmd(const struct drbd_cmd *cm __attribute((unused)),
		struct genl_info *info, void *u_ptr)
{
	unsigned minor;
	struct drbd_cfg_context cfg = { .ctx_volume = -1U };
	struct state_info si = { .current_state = 0, };
	union drbd_state state;
	int available = 0;

	if (!info)
		return 0;

	minor = ((struct drbd_genlmsghdr*)(info->userhdr))->minor;
/* variable prefix; maybe rather make that a command line parameter?
 * or use "drbd_sh_status"? */
#define _P ""
	printf("%s_minor=%u\n", _P, minor);

	drbd_cfg_context_from_attrs(&cfg, info);
	printf("%s_res_name=%s\n", _P, shell_escape(cfg.ctx_resource_name));
	printf("%s_volume=%d\n", _P, cfg.ctx_volume);

	if (state_info_from_attrs(&si, info) == 0)
		available = 1;
	state.i = si.current_state;

	if (state.conn == C_STANDALONE && state.disk == D_DISKLESS && state.role != R_PRIMARY) {
		printf("%s_known=%s\n\n", _P,
			available ? "Unconfigured"
			          : "NA # not available or not yet created");
		printf("%s_cstate=Unconfigured\n", _P);
		printf("%s_role=\n", _P);
		printf("%s_peer=\n", _P);
		printf("%s_disk=\n", _P);
		printf("%s_pdsk=\n", _P);
		printf("%s_flags_susp=\n", _P);
		printf("%s_flags_aftr_isp=\n", _P);
		printf("%s_flags_peer_isp=\n", _P);
		printf("%s_flags_user_isp=\n", _P);
		printf("%s_resynced_percent=\n", _P);
	} else {
		printf( "%s_known=Configured\n\n"
			/* connection state */
			"%s_cstate=%s\n"
			/* role */
			"%s_role=%s\n"
			"%s_peer=%s\n"
			/* disk state */
			"%s_disk=%s\n"
			"%s_pdsk=%s\n\n",
			_P,
			_P, drbd_conn_str(state.conn),
			_P, drbd_role_str(state.role),
			_P, drbd_role_str(state.peer),
			_P, drbd_disk_str(state.disk),
			_P, drbd_disk_str(state.pdsk));

		/* io suspended ? */
		printf("%s_flags_susp=%s\n", _P, state.susp ? "1" : "");
		/* reason why sync is paused */
		printf("%s_flags_aftr_isp=%s\n", _P, state.aftr_isp ? "1" : "");
		printf("%s_flags_peer_isp=%s\n", _P, state.peer_isp ? "1" : "");
		printf("%s_flags_user_isp=%s\n\n", _P, state.user_isp ? "1" : "");

		printf("%s_resynced_percent=", _P);

		if (ntb(T_bits_rs_total)) {
			uint32_t shift = si.bits_rs_total >= (1ULL << 32) ? 16 : 10;
			uint64_t left = (si.bits_oos - si.bits_rs_failed) >> shift;
			uint64_t total = 1UL + (si.bits_rs_total >> shift);
			uint64_t tmp = 1000UL - left * 1000UL/total;

			unsigned synced = tmp;
			printf("%i.%i\n", synced / 10, synced % 10);
			/* what else? everything available! */
		} else
			printf("\n");
	}
	printf("\n%s_sh_status_process\n\n\n", _P);

	fflush(stdout);
	return 0;
#undef _P
}

static int role_scmd(const struct drbd_cmd *cm __attribute((unused)),
		struct genl_info *info, void *u_ptr)
{
	union drbd_state state = { .i = 0 };

	if (!strcmp(cm->cmd, "state")) {
		fprintf(stderr, "'%s ... state' is deprecated, use '%s ... role' instead.\n",
			cmdname, cmdname);
	}

	if (!info)
		return 0;

	if (global_attrs[DRBD_NLA_STATE_INFO]) {
		drbd_nla_parse_nested(nested_attr_tb,
				      ARRAY_SIZE(state_info_nl_policy) - 1,
				      global_attrs[DRBD_NLA_STATE_INFO],
				      state_info_nl_policy);
		if (ntb(T_current_state))
			state.i = nla_get_u32(ntb(T_current_state));
	}
	if (state.conn == C_STANDALONE &&
	    state.disk == D_DISKLESS) {
		printf("Unconfigured\n");
	} else {
		printf("%s/%s\n",drbd_role_str(state.role),drbd_role_str(state.peer));
	}
	return 0;
}

static int cstate_scmd(const struct drbd_cmd *cm __attribute((unused)),
		struct genl_info *info, void *u_ptr)
{
	union drbd_state state = { .i = 0 };

	if (!info)
		return 0;

	if (global_attrs[DRBD_NLA_STATE_INFO]) {
		drbd_nla_parse_nested(nested_attr_tb,
				      ARRAY_SIZE(state_info_nl_policy) - 1,
				      global_attrs[DRBD_NLA_STATE_INFO],
				      state_info_nl_policy);
		if (ntb(T_current_state))
			state.i = nla_get_u32(ntb(T_current_state));
	}
	if (state.conn == C_STANDALONE &&
	    state.disk == D_DISKLESS) {
		printf("Unconfigured\n");
	} else {
		printf("%s\n",drbd_conn_str(state.conn));
	}
	return 0;
}

static int dstate_scmd(const struct drbd_cmd *cm __attribute((unused)),
		struct genl_info *info, void *u_ptr)
{
	union drbd_state state = { .i = 0 };

	if (!info)
		return 0;

	if (global_attrs[DRBD_NLA_STATE_INFO]) {
		drbd_nla_parse_nested(nested_attr_tb,
				      ARRAY_SIZE(state_info_nl_policy)-1,
				      global_attrs[DRBD_NLA_STATE_INFO],
				      state_info_nl_policy);
		if (ntb(T_current_state))
			state.i = nla_get_u32(ntb(T_current_state));
	}
	if ( state.conn == C_STANDALONE &&
	     state.disk == D_DISKLESS) {
		printf("Unconfigured\n");
	} else {
		printf("%s/%s\n",drbd_disk_str(state.disk),drbd_disk_str(state.pdsk));
	}
	return 0;
}

static int uuids_scmd(const struct drbd_cmd *cm,
		struct genl_info *info, void *u_ptr)
{
	union drbd_state state = { .i = 0 };
	uint64_t ed_uuid;
	uint64_t *uuids = NULL;
	int flags = flags;

	if (!info)
		return 0;

	if (global_attrs[DRBD_NLA_STATE_INFO]) {
		drbd_nla_parse_nested(nested_attr_tb,
				      ARRAY_SIZE(state_info_nl_policy)-1,
				      global_attrs[DRBD_NLA_STATE_INFO],
				      state_info_nl_policy);
		if (ntb(T_current_state))
			state.i = nla_get_u32(ntb(T_current_state));
		if (ntb(T_uuids))
			uuids = nla_data(ntb(T_uuids));
		if (ntb(T_disk_flags))
			flags = nla_get_u32(ntb(T_disk_flags));
		if (ntb(T_ed_uuid))
			ed_uuid = nla_get_u64(ntb(T_ed_uuid));
	}
	if (state.conn == C_STANDALONE &&
	    state.disk == D_DISKLESS) {
		fprintf(stderr, "Device is unconfigured\n");
		return 1;
	}
	if (state.disk == D_DISKLESS) {
		/* XXX we could print the ed_uuid anyways: */
		if (0)
			printf(X64(016)"\n", ed_uuid);
		fprintf(stderr, "Device has no disk\n");
		return 1;
	}
	if (uuids) {
		if(!strcmp(cm->cmd,"show-gi")) {
			dt_pretty_print_uuids(uuids,flags);
		} else if(!strcmp(cm->cmd,"get-gi")) {
			dt_print_uuids(uuids,flags);
		} else {
			ASSERT( 0 );
		}
	} else {
		fprintf(stderr, "No uuids found in reply!\n"
			"Maybe you need to upgrade your userland tools?\n");
	}
	return 0;
}

static int down_cmd(const struct drbd_cmd *cm, int argc, char **argv)
{
	struct minors_list *minors, *m;
	int rv;
	int success;

	if(argc > 2) {
		warn_print_excess_args(argc, argv, 2);
		return OTHER_ERROR;
	}

	minors = enumerate_minors();
	rv = _generic_config_cmd(cm, argc, argv, 1);
	success = (rv >= SS_SUCCESS && rv < ERR_CODE_BASE) || rv == NO_ERROR;
	if (success) {
		for (m = minors; m; m = m->next)
			unregister_minor(m->minor);
		free_minors(minors);
		unregister_resource(objname);
	} else {
		free_minors(minors);
		return print_config_error(rv, NULL);
	}
	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 (*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");
}

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 0
	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));
	}
#endif
	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)
		wrap_printf(4, " suspended:%s", susp_str(&resource->info));
#if 0
	if (opt_verbose || resource->info.res_weak)
		wrap_printf(4, " weak:%s",
			    resource->info.res_weak ? "yes" : "no");
#endif
	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;
	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, true),
		    drbd_disk_str(disk_state),
		    disk_state_color_stop(disk_state, true));
	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 *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;
}

const char *drbd_repl_str9(enum drbd_conns s)
{
	static const char *n[] = {
		[C_WF_REPORT_PARAMS] = "Off",
		[C_CONNECTED] = "Established",
	};

	return (s == C_WF_REPORT_PARAMS || s == C_CONNECTED) ? n[s] : drbd_conn_str(s);
}

const char *drbd_conn_str9(enum drbd_conns s)
{
	static const char *n[] = {
		[C_WF_CONNECTION] = "Connecting",
		[C_WF_REPORT_PARAMS] = "Connected",
	};

	return (s == C_WF_CONNECTION || s == C_WF_REPORT_PARAMS) ? n[s] : drbd_conn_str(s);
}

static void peer_device_status(struct peer_devices_list *peer_device, bool single_device)
{
	int indent = 4;

	if (opt_verbose || !(single_device && peer_device->ctx.ctx_volume == 0)) {
		wrap_printf(indent, "volume:%d", peer_device->ctx.ctx_volume);
		indent = 8;
	}
	/* this > C_WF_REPORT_PARAMS is > L_ESTABLISHED in DRBD 9 */
	if (opt_verbose || peer_device->info.peer_repl_state > C_WF_REPORT_PARAMS) {
		enum drbd_conns repl_state = peer_device->info.peer_repl_state;

		wrap_printf(indent, " replication:%s%s%s",
			    repl_state_color_start(repl_state),
			    drbd_repl_str9(repl_state),
			    repl_state_color_stop(repl_state));
		indent = 8;
	}
	/* this C_WF_REPORT_PARAMS is C_CONNECTED resp. L_OFF in DRBD 9 */
	if (opt_verbose || opt_statistics ||
	    peer_device->info.peer_repl_state != C_WF_REPORT_PARAMS ||
	    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, false),
			    drbd_disk_str(disk_state),
			    disk_state_color_stop(disk_state, false));
		indent = 8;
		if (peer_device->info.peer_repl_state >= C_SYNC_SOURCE &&
		    peer_device->info.peer_repl_state <= C_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 (!endpoints_equal(ctx, &peer_device->ctx))
			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)
{
	wrap_printf(2, "%s", "peer" /* connection->ctx.ctx_conn_name */);

	/* We do not want the IP-pair information */
	/* We do not have any node-id information */

	/* this C_WF_REPORT_PARAMS is C_CONNECTED in DRBD 9 */
	if (opt_verbose || connection->info.conn_connection_state < C_WF_REPORT_PARAMS) {
		enum drbd_conns cstate = connection->info.conn_connection_state;
		wrap_printf(6, " connection:%s%s%s",
			    cstate_color_start(cstate),
			    drbd_conn_str9(cstate),
			    cstate_color_stop(cstate));
	}
	if (opt_verbose || connection->info.conn_connection_state == C_WF_REPORT_PARAMS) {
		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_WF_REPORT_PARAMS)
		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 void print_usage_and_exit(const char *addinfo);

static int status_cmd(const 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;
	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 (!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
				print_usage_and_exit("unknown --color argument");
			break;
		}
	}

	resources = sort_resources(list_resources());

	sigaction(SIGHUP, &sa, NULL);
	sigaction(SIGINT, &sa, NULL);
	sigaction(SIGPIPE, &sa, NULL);
	sigaction(SIGTERM, &sa, NULL);

	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;

		if (strcmp(objname, "all") && strcmp(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);

		link_peer_devices_to_devices(peer_devices, devices);

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

	free_resources(resources);
	if (!found && strcmp(objname, "all")) {
		fprintf(stderr, "%s: No such resource\n", objname);
		return 10;
	}
	return 0;
}

static int event_key(char *key, int size, const char *name, unsigned minor,
		     struct drbd_cfg_context *ctx)
{
	int ret, pos = 0;

	ret = snprintf(key + pos, size,
		       "%s", name);
	if (ret < 0)
		return ret;
	pos += ret;
	if (size)
		size -= ret;

	ret = snprintf(key + pos, size,
		       " name:%s", ctx->ctx_resource_name);
	if (ret < 0)
		return ret;
	pos += ret;
	if (size)
		size -= ret;

	/* 8.4 drbd_cfg_context does not provide ctx->ctx_peer_node_id
	 * check the corresponding name and fake it to 0 */
	if (!strcmp(name, "connection") || !strcmp(name, "peer-device") || !strcmp(name, "helper")) {
		ret = snprintf(key + pos, size,
			      " peer-node-id:%d", 0);
		if (ret < 0)
			return ret;
		pos += ret;
		if (size)
			size -= ret;
	}
	/* Always use "peer" as connection name,
	 * and print it if ctx has peer address set.
	 * Do not show IP address pairs */
	if (ctx->ctx_peer_addr_len) {
		ret = snprintf(key + pos, size, " conn-name:%s", "peer");
		if (ret < 0)
			return ret;
		pos += ret;
		if (size)
			size -= ret;
	}
	if (ctx->ctx_volume != -1U) {
		ret = snprintf(key + pos, size,
			      " volume:%u", ctx->ctx_volume);
		if (ret < 0)
			return ret;
		pos += ret;
		if (size)
			size -= ret;
	}
	if (minor != -1U) {
		ret = snprintf(key + pos, size,
			      " minor:%u", minor);
		if (ret < 0)
			return ret;
		pos += ret;
		/* if (size) */
		/* 	size -= ret; */
	}
	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(const 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",
	};
	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 };
	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) {
			bool have_new_stats = true;
			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;
			}
			memset(&new.s, -1, sizeof(new.s));
			if (resource_statistics_from_attrs(&new.s, info)) {
				dbg(1, "resource statistics missing\n");
				have_new_stats = false;
			}
			old = update_info(&key, &new, sizeof(new));
			if (old && !have_new_stats)
				new.s = old->s;

			if (!old || new.i.res_role != old->i.res_role)
				printf(" role:%s",
				       drbd_role_str(new.i.res_role));
			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)
				printf(" suspended:%s",
				       susp_str(&new.i));
			if (opt_statistics && have_new_stats)
				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) {
			bool have_new_stats = true;
			struct {
				struct device_info i;
				struct device_statistics s;
			} *old, new;

			if (device_info_from_attrs(&new.i, info)) {
				dbg(1, "device info missing\n");
				goto nl_out;
			}
			memset(&new.s, -1, sizeof(new.s));
			if (device_statistics_from_attrs(&new.s, info)) {
				dbg(1, "device statistics missing\n");
				have_new_stats = false;
			}
			old = update_info(&key, &new, sizeof(new));
			if (old && !have_new_stats)
				new.s = old->s;
			if (!old || new.i.dev_disk_state != old->i.dev_disk_state)
				printf(" disk:%s",
				       drbd_disk_str(new.i.dev_disk_state));
			if (opt_statistics && have_new_stats)
				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) {
			bool have_new_stats = true;
			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;
			}
			memset(&new.s, -1, sizeof(new.s));
			if (connection_statistics_from_attrs(&new.s, info)) {
				dbg(1, "connection statistics missing\n");
				have_new_stats = false;
			}
			old = update_info(&key, &new, sizeof(new));
			if (old && !have_new_stats)
				new.s = old->s;
			if (!old ||
			    new.i.conn_connection_state != old->i.conn_connection_state)
				printf(" connection:%s",
				       drbd_conn_str9(new.i.conn_connection_state));
			if (!old ||
			    new.i.conn_role != old->i.conn_role)
				printf(" role:%s",
				       drbd_role_str(new.i.conn_role));
			if (opt_statistics && have_new_stats)
				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) {
			bool have_new_stats = true;
			struct {
				struct peer_device_info i;
				struct peer_device_statistics s;
			} *old, new;

			if (peer_device_info_from_attrs(&new.i, info)) {
				dbg(1, "peer device info missing\n");
				goto nl_out;
			}
			memset(&new.s, -1, sizeof(new.s));
			if (peer_device_statistics_from_attrs(&new.s, info)) {
				dbg(1, "peer device statistics missing\n");
				have_new_stats = false;
			}
			old = update_info(&key, &new, sizeof(new));
			if (old && !have_new_stats)
				new.s = old->s;

			if (!old || new.i.peer_repl_state != old->i.peer_repl_state)
				printf(" replication:%s",
				       drbd_repl_str9(new.i.peer_repl_state));
			if (!old || new.i.peer_disk_state != old->i.peer_disk_state)
				printf(" peer-disk:%s",
				       drbd_disk_str(new.i.peer_disk_state));
			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 && have_new_stats)
				print_peer_device_statistics(0, old ? &old->s : NULL,
							     &new.s, nowrap_printf);
			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);
}

/* printf format for minor, resource name, volume */
#define MNV_FMT	"%d,%s[%d]"
static void print_state(char *tag, unsigned seq, unsigned minor,
		const char *resource_name, unsigned vnr, __u32 state_i)
{
	union drbd_state s = { .i = state_i };
	printf("%u %s " MNV_FMT " { cs:%s ro:%s/%s ds:%s/%s %c%c%c%c }\n",
	       seq,
	       tag,
	       minor, resource_name, vnr,
	       drbd_conn_str(s.conn),
	       drbd_role_str(s.role),
	       drbd_role_str(s.peer),
	       drbd_disk_str(s.disk),
	       drbd_disk_str(s.pdsk),
	       s.susp ? 's' : 'r',
	       s.aftr_isp ? 'a' : '-',
	       s.peer_isp ? 'p' : '-',
	       s.user_isp ? 'u' : '-' );
}

static int print_broadcast_events(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
	struct drbd_cfg_context cfg = { .ctx_volume = -1U };
	struct state_info si = { .current_state = 0 };
	struct disk_conf dc = { .disk_size = 0, };
	struct net_conf nc = { .timeout = 0, };
	struct drbd_genlmsghdr *dh;

	/* End of initial dump. Ignore. Maybe: print some marker? */
	if (!info)
		return 0;

	dh = info->userhdr;
	if (dh->ret_code == ERR_MINOR_INVALID && cm->missing_ok)
		return 0;

	if (drbd_cfg_context_from_attrs(&cfg, info)) {
		dbg(1, "unexpected packet, configuration context missing!\n");
		/* keep running anyways. */
		struct nlattr *nla = NULL;
		if (info->attrs[DRBD_NLA_CFG_REPLY])
			nla = drbd_nla_find_nested(ARRAY_SIZE(drbd_cfg_reply_nl_policy) - 1,
						   info->attrs[DRBD_NLA_CFG_REPLY], T_info_text);
		if (nla) {
			char *txt = nla_data(nla);
			char *c;
			for (c = txt; *c; c++)
				if (*c == '\n')
					*c = '_';
			printf("%u # %s\n", info->seq, txt);
		}
		goto out;
	}

	if (state_info_from_attrs(&si, info)) {
		/* this is a DRBD_ADM_GET_STATUS reply
		 * with information about a resource without any volumes */
		printf("%u R - %s\n", info->seq, cfg.ctx_resource_name);
		goto out;
	}

	disk_conf_from_attrs(&dc, info);
	net_conf_from_attrs(&nc, info);

	switch (si.sib_reason) {
	case SIB_STATE_CHANGE:
		print_state("ST-prev", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.prev_state);
		print_state("ST-new", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.new_state);
		/* fall through */
	case SIB_GET_STATUS_REPLY:
		print_state("ST", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.current_state);
		break;
	case SIB_HELPER_PRE:
		printf("%u UH " MNV_FMT " %s\n", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.helper);
		break;
	case SIB_HELPER_POST:
		printf("%u UH-post " MNV_FMT " %s 0x%04x\n", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.helper, si.helper_exit_code);
		break;
	case SIB_SYNC_PROGRESS:
		{
		uint32_t shift = si.bits_rs_total >= (1ULL << 32) ? 16 : 10;
		uint64_t left = (si.bits_oos - si.bits_rs_failed) >> shift;
		uint64_t total = 1UL + (si.bits_rs_total >> shift);
		uint64_t tmp = 1000UL - left * 1000UL/total;

		unsigned synced = tmp;
		printf("%u SP " MNV_FMT " %i.%i\n", info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				synced / 10, synced % 10);
		}
		break;
	default:
		/* we could add the si.reason */
		printf("%u ?? " MNV_FMT " <other message, state info broadcast reason:%u>\n",
				info->seq,
				dh->minor, cfg.ctx_resource_name, cfg.ctx_volume,
				si.sib_reason);
		break;
	}
out:
	fflush(stdout);

	return 0;
}

static int w_connected_state(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
	struct state_info si = { .current_state = 0 };
	union drbd_state state;

	if (!info)
		return 0;

	if (!global_attrs[DRBD_NLA_STATE_INFO])
		return 0;

	if (state_info_from_attrs(&si, info)) {
		fprintf(stderr,"nla_policy violation!?\n");
		return 0;
	}

	if (si.sib_reason != SIB_STATE_CHANGE &&
	    si.sib_reason != SIB_GET_STATUS_REPLY)
		return 0;

	state.i = si.current_state;
	if (state.conn >= C_CONNECTED)
		return -1;  /* done waiting */
	if (state.conn < C_UNCONNECTED) {
		struct drbd_genlmsghdr *dhdr = info->userhdr;
		struct drbd_cfg_context cfg = { .ctx_volume = -1U };

		if (!wait_after_split_brain)
			return -1;  /* done waiting */
		drbd_cfg_context_from_attrs(&cfg, info);

		fprintf(stderr, "\ndrbd%u (%s[%u]) is %s, "
			       "but I'm configured to wait anways (--wait-after-sb)\n",
			       dhdr->minor,
			       cfg.ctx_resource_name, cfg.ctx_volume,
			       drbd_conn_str(state.conn));
	}

	return 0;
}

static int w_synced_state(const struct drbd_cmd *cm, struct genl_info *info, void *u_ptr)
{
	struct state_info si = { .current_state = 0 };
	union drbd_state state;

	if (!info)
		return 0;

	if (!global_attrs[DRBD_NLA_STATE_INFO])
		return 0;

	if (state_info_from_attrs(&si, info)) {
		fprintf(stderr,"nla_policy violation!?\n");
		return 0;
	}

	if (si.sib_reason != SIB_STATE_CHANGE &&
	    si.sib_reason != SIB_GET_STATUS_REPLY)
		return 0;

	state.i = si.current_state;

	if (state.conn == C_CONNECTED)
		return -1;  /* done waiting */

	if (!wait_after_split_brain && state.conn < C_UNCONNECTED)
		return -1;  /* done waiting */

	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(const struct drbd_cmd *cm, enum usage_type ut)
{
	struct drbd_argument *args;

	if(ut == XML) {
		enum cfg_ctx_key ctx = cm->ctx_key;

		printf("<command name=\"%s\">\n", cm->cmd);
		if (ctx & CTX_RESOURCE_AND_CONNECTION)
			ctx = CTX_RESOURCE | CTX_CONNECTION;
		if (ctx & (CTX_RESOURCE | CTX_MINOR | CTX_ALL)) {
			bool more_than_one_choice =
				!power_of_two(ctx & (CTX_RESOURCE | CTX_MINOR | CTX_ALL));
			const char *indent = "\t\t" + !more_than_one_choice;
			if (more_than_one_choice)
				printf("\t<group>\n");
			if (ctx & CTX_RESOURCE)
				printf("%s<argument>resource</argument>\n", indent);
			if (ctx & CTX_MINOR)
				printf("%s<argument>minor</argument>\n", indent);
			if (ctx & CTX_ALL)
				printf("%s<argument>all</argument>\n", indent);
			if (more_than_one_choice)
				printf("\t</group>\n");
		}
		if (ctx & CTX_CONNECTION) {
			printf("\t<argument>local_addr</argument>\n");
			printf("\t<argument>remote_addr</argument>\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->describe_xml(field);
		}
		printf("</command>\n");
		return;
	}

	if (ut == BRIEF)
		wrap_printf(4, "%-18s  ", cm->cmd);
	else {
		wrap_printf(0, "USAGE:\n");

		wrap_printf(1, "%s %s", progname, cm->cmd);
		if (cm->ctx_key && ut != BRIEF) {
			enum cfg_ctx_key ctx = cm->ctx_key;

			if (ctx & CTX_RESOURCE_AND_CONNECTION)
				ctx = CTX_RESOURCE | CTX_CONNECTION;
			if (ctx & (CTX_RESOURCE | CTX_MINOR | CTX_ALL)) {
				bool first = true;

				wrap_printf(4, " {");
				if (ctx & CTX_RESOURCE) {
					wrap_printf(4, "%s", "|resource" + first);
					first = false;
				}
				if (ctx & CTX_MINOR) {
					wrap_printf(4, "%s", "|minor" + first);
					first = false;
				}
				if (ctx & CTX_ALL) {
					wrap_printf(4, "%s", "|all" + first);
					first = false;
				}
				wrap_printf(4, "}");
			}
			if (ctx & CTX_CONNECTION) {
				wrap_printf(4, " [{af}:]{local_addr}[:{port}]");
				wrap_printf(4, " [{af}:]{remote_addr}[:{port}]");
			}
		}

		if (cm->drbd_args) {
			for (args = cm->drbd_args; args->name; args++)
				wrap_printf(4, " {%s}", args->name);
		}

		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->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("\nUSAGE: %s command device arguments options\n\n"
	       "Device is usually /dev/drbdX or /dev/drbd/X.\n"
	       "\nCommands are:\n",cmdname);


	for (i = 0; i < ARRAY_SIZE(commands); i++)
		print_command_usage(&commands[i], BRIEF);

	printf("\n\n"
	       "To get more details about a command issue "
	       "'drbdsetup help cmd'.\n"
	       "\n");
	/*
	printf("\n\nVersion: "PACKAGE_VERSION" (api:%d)\n%s\n",
	       API_VERSION, drbd_buildtag());
	*/
	if (addinfo)
		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 && 0 == system("/sbin/modprobe drbd")) {
		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;
}

void exec_legacy_drbdsetup(char **argv)
{
#ifdef DRBD_LEGACY_83
	static const char * const legacy_drbdsetup = "drbdsetup-83";
	char *progname, *drbdsetup;

	/* in case drbdsetup is called with an absolute or relative pathname
	 * look for the v83 drbdsetup binary in the same location,
	 * otherwise, just let execvp sort it out... */
	if ((progname = strrchr(argv[0], '/')) == 0) {
		drbdsetup = strdup(legacy_drbdsetup);
	} else {
		size_t len_dir, l;

		++progname;
		len_dir = progname - argv[0];

		l = len_dir + strlen(legacy_drbdsetup) + 1;
		drbdsetup = malloc(l);
		if (!drbdsetup) {
			fprintf(stderr, "Malloc() failed\n");
			exit(20);
		}
		strncpy(drbdsetup, argv[0], len_dir);
		strcpy(drbdsetup + len_dir, legacy_drbdsetup);
	}
	execvp(drbdsetup, argv);
#else
	fprintf(stderr, "This drbdsetup was not built with support for drbd-8.3\n"
		"Consider to rebuild with ./configure --with-83-support\n");
#endif
}

int main(int argc, char **argv)
{
	const struct drbd_cmd *cmd;
	struct option *options;
	int c, rv = 0;
	int longindex, first_optind;

	if (argv == NULL || argc < 1) {
		fputs("drbdsetup: Nonexistent or empty arguments array, aborting.\n", stderr);
		abort();
	}

	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);

	cmd = find_cmd_by_name(argv[1]);
	if (!cmd)
		print_usage_and_exit("invalid command");

	if (!modprobe_drbd()) {
		if (!strcmp(argv[0], "down") ||
		    !strcmp(argv[0], "secondary") ||
		    !strcmp(argv[0], "disconnect") ||
		    !strcmp(argv[0], "detach"))
			return 0; /* "down" succeeds even if drbd is missing */
		return 20;
	}

	if (try_genl) {
		if (cmd->continuous_poll && kernel_older_than(2, 6, 23))
			drbd_genl_family.nl_groups = -1;
		drbd_sock = genl_connect_to_family(&drbd_genl_family);
		if (!drbd_sock) {
			try_genl = 0;
			exec_legacy_drbdsetup(argv);
			/* Only reached in case exec() failed... */
			fprintf(stderr, "Could not connect to 'drbd' generic netlink family\n");
			return 20;
		}
		if (drbd_genl_family.version != API_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",
				API_VERSION, drbd_genl_family.version,
				(unsigned)sizeof(struct drbd_genlmsghdr),
				drbd_genl_family.hdrsize);
			return 20;
		}
	}

	/* Make argv[0] the command name so that getopt_long() will leave it in
	 * the first position. */
	argv++;
	argc--;

	options = make_longoptions(cmd);
	for (;;) {
		c = getopt_long(argc, argv, "(", 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;

	context = 0;
	if (cmd->ctx_key & (CTX_MINOR | CTX_RESOURCE | CTX_ALL | CTX_RESOURCE_AND_CONNECTION)) {
		if (argc == optind &&
		    !(cmd->ctx_key & (CTX_RESOURCE_AND_CONNECTION | CTX_CONNECTION)) &&
		    (cmd->ctx_key & CTX_ALL)) {
			context |= CTX_ALL;  /* assume "all" if no argument is given */
			objname = "all";
		} else {
			if (argc <= optind) {
				fprintf(stderr, "Missing first argument\n");
				print_command_usage(cmd, FULL);
				exit(20);
			}
			objname = argv[optind++];
			ensure_sanity_of_res_name(objname);
			if (!strcmp(objname, "all")) {
				if (!(cmd->ctx_key & CTX_ALL))
					print_usage_and_exit("command does not accept argument 'all'");
				context = CTX_ALL;
			} else if (cmd->ctx_key & CTX_MINOR) {
				minor = dt_minor_of_dev(objname);
				if (minor != -1U)
					context = CTX_MINOR;
				else if (!(cmd->ctx_key &
						(CTX_RESOURCE | CTX_RESOURCE_AND_CONNECTION))) {
					fprintf(stderr, "Cannot determine minor device number of "
							"device '%s'\n",
						objname);
					exit(20);
				}
			}
			/* It could have been "all", but was not.
			 * It could have been a minor number (or device node name), but was not.
			 * So it has to be a resource,
			 * or a resource and possibly connection specification.
			 * (CTX_CONNECTION alone will not enter this branch). */
			if (!context)
				context = CTX_RESOURCE;
		}
	}
	if (cmd->ctx_key & (CTX_CONNECTION | CTX_RESOURCE_AND_CONNECTION)) {
		if (argc <= optind + 1) {
			fprintf(stderr, "Missing connection endpoint argument\n");
			print_command_usage(cmd, FULL);
			exit(20);
		}
		opt_local_addr = argv[optind++];
		opt_peer_addr = argv[optind++];
		context |= CTX_CONNECTION;
	}

	/* 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 == NULL)
		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
