| /* |
| * 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 }; |
| |
| if (!info) |
| return 0; |
| |
| drbd_cfg_context_from_attrs(&cfg, info); |
| if (cfg.ctx_resource_name) { |
| struct resources_list *r = calloc(1, sizeof(*r)); |
| struct nlattr *res_opts = global_attrs[DRBD_NLA_RESOURCE_OPTS]; |
| |
| r->name = strdup(cfg.ctx_resource_name); |
| if (res_opts) { |
| int size = nla_total_size(nla_len(res_opts)); |
| |
| r->res_opts = malloc(size); |
| memcpy(r->res_opts, res_opts, size); |
| } |
| resource_info_from_attrs(&r->info, info); |
| memset(&r->statistics, -1, sizeof(r->statistics)); |
| resource_statistics_from_attrs(&r->statistics, info); |
| **tail = r; |
| *tail = &r->next; |
| } |
| return 0; |
| } |
| |
| static void free_resources(struct resources_list *resources) |
| { |
| while (resources) { |
| struct resources_list *r = resources; |
| resources = resources->next; |
| free(r->name); |
| free(r->res_opts); |
| free(r); |
| } |
| } |
| |
| static int resource_name_cmp(const struct resources_list * const *a, const struct resources_list * const *b) |
| { |
| return strcmp((*a)->name, (*b)->name); |
| } |
| |
| static struct resources_list *sort_resources(struct resources_list *resources) |
| { |
| struct resources_list *r; |
| int n; |
| |
| for (r = resources, n = 0; r; r = r->next) |
| n++; |
| if (n > 1) { |
| struct resources_list **array; |
| |
| array = malloc(sizeof(*array) * n); |
| for (r = resources, n = 0; r; r = r->next) |
| array[n++] = r; |
| qsort(array, n, sizeof(*array), (int (*)(const void *, const void *)) resource_name_cmp); |
| n--; |
| array[n]->next = NULL; |
| for (; n > 0; n--) |
| array[n - 1]->next = array[n]; |
| resources = array[0]; |
| free(array); |
| } |
| return resources; |
| } |
| |
| /* |
| * Expects objname to be set to the resource name or "all". |
| */ |
| static struct resources_list *list_resources(void) |
| { |
| struct drbd_cmd cmd = { |
| .cmd_id = DRBD_ADM_GET_RESOURCES, |
| .show_function = remember_resource, |
| .missing_ok = false, |
| }; |
| struct resources_list *list = NULL, **tail = &list; |
| char *old_objname = objname; |
| unsigned old_minor = minor; |
| int 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 }; |
| |
| if (!info) |
| return 0; |
| |
| drbd_cfg_context_from_attrs(&ctx, info); |
| if (ctx.ctx_resource_name) { |
| struct connections_list *c = calloc(1, sizeof(*c)); |
| struct nlattr *net_conf = global_attrs[DRBD_NLA_NET_CONF]; |
| |
| 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 }; |
| |
| if (!info) |
| return 0; |
| |
| drbd_cfg_context_from_attrs(&ctx, info); |
| if (ctx.ctx_resource_name) { |
| struct peer_devices_list *p = calloc(1, sizeof(*p)); |
| |
| 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.backing_dev) { |
| fprintf(stderr, "Has no disk config, try with drbdmeta.\n"); |
| return 1; |
| } |
| |
| 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); |
| if (cfg.ctx_resource_name) |
| 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; |
| if (ctx->ctx_resource_name) { |
| 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) { |
| struct { |
| struct resource_info i; |
| struct resource_statistics s; |
| } *old, new; |
| |
| if (resource_info_from_attrs(&new.i, info)) { |
| dbg(1, "resource info missing\n"); |
| goto nl_out; |
| } |
| old = update_info(&key, &new, sizeof(new)); |
| if (!old || new.i.res_role != old->i.res_role) |
| printf(" role:%s", |
| 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) { |
| if (resource_statistics_from_attrs(&new.s, info)) { |
| dbg(1, "resource statistics missing\n"); |
| if (old) |
| new.s = old->s; |
| } else |
| print_resource_statistics(0, old ? &old->s : NULL, |
| &new.s, nowrap_printf); |
| } |
| free(old); |
| } else |
| update_info(&key, NULL, 0); |
| break; |
| case DRBD_DEVICE_STATE: |
| if (action != NOTIFY_DESTROY) { |
| struct { |
| struct device_info i; |
| struct device_statistics s; |
| } *old, new; |
| |
| if (device_info_from_attrs(&new.i, info)) { |
| dbg(1, "device info missing\n"); |
| goto nl_out; |
| } |
| old = update_info(&key, &new, sizeof(new)); |
| if (!old || new.i.dev_disk_state != old->i.dev_disk_state) |
| printf(" disk:%s", |
| drbd_disk_str(new.i.dev_disk_state)); |
| if (opt_statistics) { |
| if (device_statistics_from_attrs(&new.s, info)) { |
| dbg(1, "device statistics missing\n"); |
| if (old) |
| new.s = old->s; |
| } else |
| print_device_statistics(0, old ? &old->s : NULL, |
| &new.s, nowrap_printf); |
| } |
| free(old); |
| } else |
| update_info(&key, NULL, 0); |
| break; |
| case DRBD_CONNECTION_STATE: |
| if (action != NOTIFY_DESTROY) { |
| struct { |
| struct connection_info i; |
| struct connection_statistics s; |
| } *old, new; |
| |
| if (connection_info_from_attrs(&new.i, info)) { |
| dbg(1, "connection info missing\n"); |
| goto nl_out; |
| } |
| old = update_info(&key, &new, sizeof(new)); |
| if (!old || |
| new.i.conn_connection_state != old->i.conn_connection_state) |
| printf(" connection:%s", |
| 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) { |
| if (connection_statistics_from_attrs(&new.s, info)) { |
| dbg(1, "connection statistics missing\n"); |
| if (old) |
| new.s = old->s; |
| } else |
| print_connection_statistics(0, old ? &old->s : NULL, |
| &new.s, nowrap_printf); |
| } |
| free(old); |
| } else |
| update_info(&key, NULL, 0); |
| break; |
| case DRBD_PEER_DEVICE_STATE: |
| if (action != NOTIFY_DESTROY) { |
| struct { |
| struct peer_device_info i; |
| struct peer_device_statistics s; |
| } *old, new; |
| |
| if (peer_device_info_from_attrs(&new.i, info)) { |
| dbg(1, "peer device info missing\n"); |
| goto nl_out; |
| } |
| old = update_info(&key, &new, sizeof(new)); |
| if (!old || new.i.peer_repl_state != old->i.peer_repl_state) |
| printf(" replication:%s", |
| 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) { |
| if (peer_device_statistics_from_attrs(&new.s, info)) { |
| dbg(1, "peer device statistics missing\n"); |
| if (old) |
| new.s = old->s; |
| } else |
| print_peer_device_statistics(0, old ? &old->s : NULL, |
| &new.s, nowrap_printf); |
| } |
| free(old); |
| } else |
| update_info(&key, NULL, 0); |
| break; |
| case DRBD_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; |
| |
| 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 |