Просмотр исходного кода

Add qdevice extra info to votequorum

Signed-off-by: Thomas Jones <thomas.jones@ibm.com>
Reviewed-by: Christine Caulfield <ccaulfie@redhat.com>
Reviewed-by: Jan Friesse <jfriesse@redhat.com>
Thomas Jones 4 месяцев назад
Родитель
Сommit
9d3590387a

+ 348 - 13
exec/votequorum.c

@@ -131,6 +131,13 @@ struct req_exec_quorum_qdevice_reconfigure {
 	char	newname[VOTEQUORUM_QDEVICE_MAX_NAME_LEN];
 } __attribute__((packed));
 
+struct req_exec_quorum_qdevice_set_extra_info {
+	struct      qb_ipc_request_header header __attribute__((aligned(8)));
+	uint32_t    nodeid; ///< the ID of the node whose info needs updating
+	uint32_t    ei_size;  ///< length of the extra info
+	char        extra_info[];
+} __attribute__((packed));
+
 /*
  * votequorum_exec onwire version (via totem)
  */
@@ -141,14 +148,16 @@ struct req_exec_quorum_qdevice_reconfigure {
  * votequorum_exec onwire messages (via totem)
  */
 
-#define MESSAGE_REQ_EXEC_VOTEQUORUM_NODEINFO            0
-#define MESSAGE_REQ_EXEC_VOTEQUORUM_RECONFIGURE         1
-#define MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_REG         2
-#define MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_RECONFIGURE 3
+#define MESSAGE_REQ_EXEC_VOTEQUORUM_NODEINFO               0
+#define MESSAGE_REQ_EXEC_VOTEQUORUM_RECONFIGURE            1
+#define MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_REG            2
+#define MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_RECONFIGURE    3
+#define MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_EXTRA_NODEINFO 4
 
 static void votequorum_exec_send_expectedvotes_notification(void);
 static int votequorum_exec_send_quorum_notification(void *conn, uint64_t context);
 static int votequorum_exec_send_nodelist_notification(void *conn, uint64_t context);
+static int votequorum_exec_send_all_qdevice_extra_info_notification(void *conn, uint64_t context);
 
 #define VOTEQUORUM_RECONFIG_PARAM_EXPECTED_VOTES 1
 #define VOTEQUORUM_RECONFIG_PARAM_NODE_VOTES     2
@@ -166,14 +175,15 @@ static int votequorum_exec_send_reconfigure(uint8_t param, unsigned int nodeid,
  * votequorum internal node status/view
  */
 
-#define NODE_FLAGS_QUORATE               1
-#define NODE_FLAGS_LEAVING               2
-#define NODE_FLAGS_WFASTATUS             4
-#define NODE_FLAGS_FIRST                 8
-#define NODE_FLAGS_QDEVICE_REGISTERED   16
-#define NODE_FLAGS_QDEVICE_ALIVE        32
-#define NODE_FLAGS_QDEVICE_CAST_VOTE    64
-#define NODE_FLAGS_QDEVICE_MASTER_WINS 128
+#define NODE_FLAGS_QUORATE                  1
+#define NODE_FLAGS_LEAVING                  2
+#define NODE_FLAGS_WFASTATUS                4
+#define NODE_FLAGS_FIRST                    8
+#define NODE_FLAGS_QDEVICE_REGISTERED      16
+#define NODE_FLAGS_QDEVICE_ALIVE           32
+#define NODE_FLAGS_QDEVICE_CAST_VOTE       64
+#define NODE_FLAGS_QDEVICE_MASTER_WINS    128
+#define NODE_FLAGS_QDEVICE_EXTRA_INFO_SET 256
 
 typedef enum {
 	NODESTATE_MEMBER=1,
@@ -187,6 +197,10 @@ struct cluster_node {
 	uint32_t    votes;
 	uint32_t    expected_votes;
 	uint32_t    flags;
+
+	uint32_t    ei_size;
+	char        extra_nodeinfo[VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE];
+
 	struct      qb_list_head list;
 };
 
@@ -267,6 +281,7 @@ static quorum_set_quorate_fn_t quorum_callback;
 static char *votequorum_exec_init_fn (struct corosync_api_v1 *api);
 static int votequorum_exec_exit_fn (void);
 static int votequorum_exec_send_nodeinfo(uint32_t nodeid);
+static int votequorum_exec_send_set_qdevice_extra_info(uint32_t nodeid);
 
 static void message_handler_req_exec_votequorum_nodeinfo (
 	const void *message,
@@ -288,6 +303,11 @@ static void message_handler_req_exec_votequorum_qdevice_reconfigure (
 	unsigned int nodeid);
 static void exec_votequorum_qdevice_reconfigure_endian_convert (void *message);
 
+static void message_handler_req_exec_votequorum_qdevice_set_extra_info (
+	const void *message,
+	unsigned int nodeid);
+static void exec_votequorum_qdevice_set_extra_info_endian_convert (void *message);
+
 static struct corosync_exec_handler votequorum_exec_engine[] =
 {
 	{ /* 0 */
@@ -306,6 +326,10 @@ static struct corosync_exec_handler votequorum_exec_engine[] =
 		.exec_handler_fn	= message_handler_req_exec_votequorum_qdevice_reconfigure,
 		.exec_endian_convert_fn	= exec_votequorum_qdevice_reconfigure_endian_convert
 	},
+	{ /* 4 */
+		.exec_handler_fn	= message_handler_req_exec_votequorum_qdevice_set_extra_info,
+		.exec_endian_convert_fn	= exec_votequorum_qdevice_set_extra_info_endian_convert
+	},
 };
 
 /*
@@ -348,6 +372,12 @@ static void message_handler_req_lib_votequorum_qdevice_poll (void *conn,
 static void message_handler_req_lib_votequorum_qdevice_master_wins (void *conn,
 							     const void *message);
 
+static void message_handler_req_lib_votequorum_qdevice_set_extra_info (void *conn,
+							     const void *message);
+
+static void message_handler_req_lib_votequorum_qdevice_get_extra_info (void *conn,
+							     const void *message);
+
 static struct corosync_lib_handler quorum_lib_service[] =
 {
 	{ /* 0 */
@@ -389,7 +419,15 @@ static struct corosync_lib_handler quorum_lib_service[] =
 	{ /* 9 */
 		.lib_handler_fn		= message_handler_req_lib_votequorum_qdevice_master_wins,
 		.flow_control		= COROSYNC_LIB_FLOW_CONTROL_NOT_REQUIRED
-	}
+	},
+	{ /* 10 */
+		.lib_handler_fn		= message_handler_req_lib_votequorum_qdevice_set_extra_info,
+		.flow_control		= COROSYNC_LIB_FLOW_CONTROL_NOT_REQUIRED
+	},
+	{ /* 11 */
+		.lib_handler_fn		= message_handler_req_lib_votequorum_qdevice_get_extra_info,
+		.flow_control		= COROSYNC_LIB_FLOW_CONTROL_NOT_REQUIRED
+	},
 };
 
 static struct corosync_service_engine votequorum_service_engine = {
@@ -2448,6 +2486,9 @@ static int votequorum_sync_process (void)
 {
 	if (!sync_nodeinfo_sent) {
 		votequorum_exec_send_nodeinfo(us->node_id);
+		if(us->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+			votequorum_exec_send_set_qdevice_extra_info(us->node_id);
+		}
 		votequorum_exec_send_nodeinfo(VOTEQUORUM_QDEVICE_NODEID);
 		if (strlen(qdevice_name)) {
 			votequorum_exec_send_qdevice_reg(VOTEQUORUM_QDEVICE_OPERATION_REGISTER,
@@ -2666,6 +2707,9 @@ static void message_handler_req_lib_votequorum_getinfo (void *conn, const void *
 		if (node->flags & NODE_FLAGS_QDEVICE_MASTER_WINS) {
 			res_lib_votequorum_getinfo.flags |= VOTEQUORUM_INFO_QDEVICE_MASTER_WINS;
 		}
+		if (node->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+			res_lib_votequorum_getinfo.flags |= VOTEQUORUM_INFO_QDEVICE_HAS_EXTRA_INFO;
+		}
 	} else {
 		error = CS_ERR_NOT_EXIST;
 	}
@@ -2791,6 +2835,7 @@ static void message_handler_req_lib_votequorum_trackstart (void *conn,
 		log_printf(LOGSYS_LEVEL_DEBUG, "sending initial status to %p", conn);
 		votequorum_exec_send_nodelist_notification(conn, req_lib_votequorum_trackstart->context);
 		votequorum_exec_send_quorum_notification(conn, req_lib_votequorum_trackstart->context);
+		votequorum_exec_send_all_qdevice_extra_info_notification(conn, req_lib_votequorum_trackstart->context);
 	}
 
 	if (quorum_pd->tracking_enabled) {
@@ -2925,6 +2970,11 @@ static void message_handler_req_lib_votequorum_qdevice_unregister (void *conn,
 		us->flags &= ~NODE_FLAGS_QDEVICE_ALIVE;
 		us->flags &= ~NODE_FLAGS_QDEVICE_CAST_VOTE;
 		us->flags &= ~NODE_FLAGS_QDEVICE_MASTER_WINS;
+
+		us->flags &= ~NODE_FLAGS_QDEVICE_EXTRA_INFO_SET;
+		us->ei_size = 0;
+		votequorum_exec_send_set_qdevice_extra_info(us->node_id);
+
 		votequorum_exec_send_nodeinfo(us->node_id);
 		votequorum_exec_send_qdevice_reg(VOTEQUORUM_QDEVICE_OPERATION_UNREGISTER,
 						 req_lib_votequorum_qdevice_unregister->name);
@@ -3080,3 +3130,288 @@ out:
 
 	LEAVE();
 }
+
+static void fill_extra_info_notification(
+	struct res_lib_votequorum_qdevice_extra_info_notification *res_lib_votequorum_qdevice_extra_info_notification,
+	struct cluster_node *node)
+{
+	ENTER();
+
+	if(node->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+		memcpy(res_lib_votequorum_qdevice_extra_info_notification->extra_info, node->extra_nodeinfo, node->ei_size);
+		res_lib_votequorum_qdevice_extra_info_notification->ei_size = node->ei_size;
+	} else {
+		res_lib_votequorum_qdevice_extra_info_notification->ei_size = 0;
+	}
+	res_lib_votequorum_qdevice_extra_info_notification->nodeid = node->node_id;
+
+	res_lib_votequorum_qdevice_extra_info_notification->header.id = MESSAGE_RES_VOTEQUORUM_QDEVICE_EXTRA_INFO_NOTIFICATION;
+	res_lib_votequorum_qdevice_extra_info_notification->header.size = sizeof(struct res_lib_votequorum_qdevice_extra_info_notification)
+	                                                                  + res_lib_votequorum_qdevice_extra_info_notification->ei_size;
+	res_lib_votequorum_qdevice_extra_info_notification->header.error = CS_OK;
+
+	LEAVE();
+}
+
+static int votequorum_exec_send_all_qdevice_extra_info_notification(void *conn, uint64_t context)
+{
+	char buf[sizeof(struct res_lib_votequorum_qdevice_extra_info_notification) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE] = { 0 };
+	struct res_lib_votequorum_qdevice_extra_info_notification *res_lib_votequorum_qdevice_extra_info_notification
+		= (struct res_lib_votequorum_qdevice_extra_info_notification*)buf;
+	struct qb_list_head *tmp;
+	struct cluster_node *node;
+
+	ENTER();
+
+	qb_list_for_each(tmp, &cluster_members_list) {
+		node = qb_list_entry(tmp, struct cluster_node, list);
+		if(node->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+			log_printf(LOGSYS_LEVEL_DEBUG,
+			           "Sending extra info notification for node " CS_PRI_NODE_ID " %" PRIu32 " bytes.",
+			           node->node_id, node->ei_size);
+			fill_extra_info_notification(res_lib_votequorum_qdevice_extra_info_notification, node);
+			res_lib_votequorum_qdevice_extra_info_notification->context = context;
+			corosync_api->ipc_dispatch_send(conn,
+			                                res_lib_votequorum_qdevice_extra_info_notification,
+			                                res_lib_votequorum_qdevice_extra_info_notification->header.size);
+		}
+	}
+
+	LEAVE();
+
+	return CS_OK;
+}
+
+static int
+votequorum_exec_send_qdevice_extra_info_notification(uint32_t nodeid)
+{
+	char buf[sizeof(struct res_lib_votequorum_qdevice_extra_info_notification) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE] = { 0 };
+	struct res_lib_votequorum_qdevice_extra_info_notification *res_lib_votequorum_qdevice_extra_info_notification
+		= (struct res_lib_votequorum_qdevice_extra_info_notification*)buf;
+	struct quorum_pd *qpd;
+	struct qb_list_head *tmp;
+
+	ENTER();
+
+	struct cluster_node *node = find_node_by_nodeid(nodeid);
+
+	if(node == NULL) {
+		log_printf(LOGSYS_LEVEL_ERROR,
+		           "Sending extra info notification for node " CS_PRI_NODE_ID " failed, node doesn't exist",
+		           nodeid);
+		LEAVE();
+		return CS_ERR_NOT_EXIST;
+	}
+
+	log_printf(LOGSYS_LEVEL_DEBUG,
+	           "Sending extra info notification for node " CS_PRI_NODE_ID " %" PRIu32 " bytes.",
+	           nodeid, node->ei_size);
+
+	fill_extra_info_notification(res_lib_votequorum_qdevice_extra_info_notification, node);
+
+	qb_list_for_each(tmp, &trackers_list) {
+		qpd = qb_list_entry(tmp, struct quorum_pd, list);
+		log_printf(LOGSYS_LEVEL_DEBUG,
+		           "Sending extra info notification for node " CS_PRI_NODE_ID " message (ctx: %" PRIx64 ")",
+		           nodeid, qpd->tracking_context);
+		res_lib_votequorum_qdevice_extra_info_notification->context = qpd->tracking_context;
+		corosync_api->ipc_dispatch_send(qpd->conn,
+		                                res_lib_votequorum_qdevice_extra_info_notification,
+		                                res_lib_votequorum_qdevice_extra_info_notification->header.size);
+	}
+
+	LEAVE();
+
+	return CS_OK;
+}
+
+static int
+votequorum_exec_send_set_qdevice_extra_info(uint32_t nodeid)
+{
+	char buf[sizeof(struct req_exec_quorum_qdevice_set_extra_info) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE];
+	struct req_exec_quorum_qdevice_set_extra_info *req_exec_quorum_qdevice_set_extra_info
+		= (struct req_exec_quorum_qdevice_set_extra_info *)buf;
+	struct iovec iov[1];
+	struct cluster_node *node = NULL;
+	int ret = 0;
+
+	ENTER();
+
+	node = find_node_by_nodeid(nodeid);
+	if(!node) {
+		log_printf(LOGSYS_LEVEL_ERROR,
+		           "Sending mcast extra info for node " CS_PRI_NODE_ID " failed because node doesn't exist",
+		           nodeid);
+		ret = CS_ERR_NOT_EXIST;
+		goto exit;
+	}
+
+	log_printf(LOGSYS_LEVEL_DEBUG,
+	           "Sending mcast extra info for node " CS_PRI_NODE_ID " %" PRIu32 " bytes.",
+	           nodeid, node->ei_size);
+
+	req_exec_quorum_qdevice_set_extra_info->nodeid = nodeid;
+	if(node->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+		req_exec_quorum_qdevice_set_extra_info->ei_size = node->ei_size;
+		memcpy(req_exec_quorum_qdevice_set_extra_info->extra_info, node->extra_nodeinfo, node->ei_size);
+	} else {
+		req_exec_quorum_qdevice_set_extra_info->ei_size = 0;
+	}
+
+	req_exec_quorum_qdevice_set_extra_info->header.id = SERVICE_ID_MAKE(VOTEQUORUM_SERVICE,
+	                                                                    MESSAGE_REQ_EXEC_VOTEQUORUM_QDEVICE_EXTRA_NODEINFO);
+	req_exec_quorum_qdevice_set_extra_info->header.size = sizeof(struct req_exec_quorum_qdevice_set_extra_info)
+	                                                      + req_exec_quorum_qdevice_set_extra_info->ei_size;
+
+	iov[0].iov_base = (void *)buf;
+	iov[0].iov_len = req_exec_quorum_qdevice_set_extra_info->header.size;
+	ret = corosync_api->totem_mcast (iov, 1, TOTEM_AGREED);
+
+exit:
+	LEAVE();
+
+	return ret;
+}
+
+static void message_handler_req_exec_votequorum_qdevice_set_extra_info (
+	const void *message,
+	unsigned int nodeid)
+{
+	const struct req_exec_quorum_qdevice_set_extra_info *req_exec_quorum_qdevice_set_extra_info = message;
+	struct cluster_node *node = NULL;
+
+	ENTER();
+
+	log_printf(LOGSYS_LEVEL_DEBUG, "Setting extra info for node " CS_PRI_NODE_ID, nodeid);
+
+	if(req_exec_quorum_qdevice_set_extra_info->ei_size > VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE) {
+		log_printf(LOGSYS_LEVEL_ERROR, "Oversized extra info for node " CS_PRI_NODE_ID "! Ignoring", nodeid);
+		return;
+	}
+
+	node = find_node_by_nodeid(nodeid);
+	if(node) {
+		log_printf(LOGSYS_LEVEL_DEBUG,
+		           "New extra info for node " CS_PRI_NODE_ID " is %" PRIu32 " bytes",
+		           nodeid, req_exec_quorum_qdevice_set_extra_info->ei_size);
+		if(req_exec_quorum_qdevice_set_extra_info->ei_size == 0) {
+			node->flags &= ~NODE_FLAGS_QDEVICE_EXTRA_INFO_SET;
+			node->ei_size = 0;
+		} else {
+			node->ei_size = req_exec_quorum_qdevice_set_extra_info->ei_size;
+			memcpy(node->extra_nodeinfo,
+			       req_exec_quorum_qdevice_set_extra_info->extra_info,
+			       req_exec_quorum_qdevice_set_extra_info->ei_size);
+			node->flags |= NODE_FLAGS_QDEVICE_EXTRA_INFO_SET;
+		}
+		votequorum_exec_send_qdevice_extra_info_notification(nodeid);
+	} else {
+		log_printf(LOGSYS_LEVEL_ERROR, "Failed to find node " CS_PRI_NODE_ID " for set extra info", nodeid);
+	}
+
+	LEAVE();
+}
+
+static void exec_votequorum_qdevice_set_extra_info_endian_convert (void *message)
+{
+	struct req_exec_quorum_qdevice_set_extra_info *set_extra_info = message;
+	ENTER();
+
+	set_extra_info->ei_size = swab32(set_extra_info->ei_size);
+	set_extra_info->nodeid  = swab32(set_extra_info->nodeid);
+
+	LEAVE();
+}
+
+
+static void message_handler_req_lib_votequorum_qdevice_set_extra_info (void *conn,
+							     const void *message)
+{
+	const struct req_lib_votequorum_set_qdevice_extra_info *req_lib_votequorum_qdevice_set_extra_info = message;
+	struct res_lib_votequorum_status res_lib_votequorum_status;
+	cs_error_t error = CS_OK;
+
+	ENTER();
+
+	log_printf(LOGSYS_LEVEL_DEBUG,
+	           "Local set extra info %" PRIu32 " bytes",
+	           req_lib_votequorum_qdevice_set_extra_info->ei_size);
+
+	if (!qdevice_can_operate) {
+		error = CS_ERR_ACCESS;
+		goto out;
+	}
+
+	if(req_lib_votequorum_qdevice_set_extra_info->ei_size > VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE) {
+		log_printf(LOGSYS_LEVEL_ERROR, "Failed to set extra info, oversized.");
+		error = CS_ERR_INVALID_PARAM;
+		goto out;
+	}
+
+	if (us->flags & NODE_FLAGS_QDEVICE_REGISTERED) {
+		if(req_lib_votequorum_qdevice_set_extra_info->ei_size == 0) {
+			us->flags &= ~NODE_FLAGS_QDEVICE_EXTRA_INFO_SET;
+			us->ei_size = 0;
+		} else {
+			us->ei_size = req_lib_votequorum_qdevice_set_extra_info->ei_size;
+			memcpy(us->extra_nodeinfo,
+			       req_lib_votequorum_qdevice_set_extra_info->extra_info,
+			       req_lib_votequorum_qdevice_set_extra_info->ei_size);
+			us->flags |= NODE_FLAGS_QDEVICE_EXTRA_INFO_SET;
+		}
+		votequorum_exec_send_set_qdevice_extra_info(us->node_id);
+	} else {
+		error = CS_ERR_NOT_EXIST;
+	}
+
+out:
+	res_lib_votequorum_status.header.size = sizeof(res_lib_votequorum_status);
+	res_lib_votequorum_status.header.id = MESSAGE_RES_VOTEQUORUM_STATUS;
+	res_lib_votequorum_status.header.error = error;
+	corosync_api->ipc_response_send(conn, &res_lib_votequorum_status, sizeof(res_lib_votequorum_status));
+
+	LEAVE();
+}
+
+static void message_handler_req_lib_votequorum_qdevice_get_extra_info (void *conn,
+							     const void *message)
+{
+	const struct req_lib_votequorum_get_qdevice_extra_info *req_lib_votequorum_qdevice_get_extra_info = message;
+	char buf[sizeof(struct res_lib_votequorum_get_qdevice_extra_info) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE];
+	struct res_lib_votequorum_get_qdevice_extra_info *res_lib_votequorum_qdevice_get_extra_info;
+	size_t ei_size = 0;
+	cs_error_t error = CS_OK;
+
+	ENTER();
+
+	res_lib_votequorum_qdevice_get_extra_info = (struct res_lib_votequorum_get_qdevice_extra_info *)buf;
+
+	if (!qdevice_can_operate) {
+		error = CS_ERR_ACCESS;
+		goto out;
+	}
+
+	struct cluster_node *node = find_node_by_nodeid(req_lib_votequorum_qdevice_get_extra_info->nodeid);
+
+	if(!node) {
+		error = CS_ERR_NOT_EXIST;
+		goto out;
+	}
+
+	if (node->flags & NODE_FLAGS_QDEVICE_EXTRA_INFO_SET) {
+		res_lib_votequorum_qdevice_get_extra_info->ei_size = ei_size = node->ei_size;
+		memcpy(res_lib_votequorum_qdevice_get_extra_info->extra_info, node->extra_nodeinfo, node->ei_size);
+	} else {
+		res_lib_votequorum_qdevice_get_extra_info->ei_size = 0;
+	}
+
+out:
+	res_lib_votequorum_qdevice_get_extra_info->header.size = sizeof(struct res_lib_votequorum_get_qdevice_extra_info) + ei_size;
+	res_lib_votequorum_qdevice_get_extra_info->header.id = MESSAGE_RES_VOTEQUORUM_QDEVICE_GET_EXTRA_INFO;
+	res_lib_votequorum_qdevice_get_extra_info->header.error = error;
+	corosync_api->ipc_response_send(conn,
+	                                res_lib_votequorum_qdevice_get_extra_info,
+	                                res_lib_votequorum_qdevice_get_extra_info->header.size);
+
+	LEAVE();
+}

+ 31 - 1
include/corosync/ipc_votequorum.h

@@ -53,7 +53,9 @@ enum req_votequorum_types {
 	MESSAGE_REQ_VOTEQUORUM_QDEVICE_UNREGISTER,
 	MESSAGE_REQ_VOTEQUORUM_QDEVICE_UPDATE,
 	MESSAGE_REQ_VOTEQUORUM_QDEVICE_POLL,
-	MESSAGE_REQ_VOTEQUORUM_QDEVICE_MASTER_WINS
+	MESSAGE_REQ_VOTEQUORUM_QDEVICE_MASTER_WINS,
+	MESSAGE_REQ_VOTEQUORUM_QDEVICE_SET_EXTRA_INFO,
+	MESSAGE_REQ_VOTEQUORUM_QDEVICE_GET_EXTRA_INFO,
 };
 
 /**
@@ -66,6 +68,8 @@ enum res_votequorum_types {
 	MESSAGE_RES_VOTEQUORUM_QUORUM_NOTIFICATION,
 	MESSAGE_RES_VOTEQUORUM_EXPECTEDVOTES_NOTIFICATION,
 	MESSAGE_RES_VOTEQUORUM_NODELIST_NOTIFICATION,
+	MESSAGE_RES_VOTEQUORUM_QDEVICE_GET_EXTRA_INFO,
+	MESSAGE_RES_VOTEQUORUM_QDEVICE_EXTRA_INFO_NOTIFICATION,
 };
 
 /**
@@ -168,6 +172,23 @@ struct res_lib_votequorum_status {
 	struct qb_ipc_response_header header __attribute__((aligned(8)));
 };
 
+struct req_lib_votequorum_set_qdevice_extra_info {
+	struct qb_ipc_request_header header __attribute__((aligned(8)));
+	uint32_t ei_size;
+	char extra_info[];
+};
+
+struct req_lib_votequorum_get_qdevice_extra_info {
+	struct qb_ipc_request_header header __attribute__((aligned(8)));
+	int nodeid;
+};
+
+struct res_lib_votequorum_get_qdevice_extra_info {
+	struct qb_ipc_response_header header __attribute__((aligned(8)));
+	uint32_t ei_size;
+	char extra_info[];
+};
+
 #define VOTEQUORUM_INFO_TWONODE                 1
 #define VOTEQUORUM_INFO_QUORATE                 2
 #define VOTEQUORUM_INFO_WAIT_FOR_ALL	        4
@@ -236,6 +257,15 @@ struct res_lib_votequorum_expectedvotes_notification {
 	mar_uint32_t expected_votes __attribute__((aligned(8)));
 };
 
+
+struct res_lib_votequorum_qdevice_extra_info_notification {
+	struct qb_ipc_response_header header __attribute__((aligned(8)));
+	mar_uint64_t context __attribute__((aligned(8)));
+	mar_uint32_t nodeid __attribute__((aligned(8)));
+	mar_uint32_t ei_size __attribute__((aligned(8)));
+	char extra_info[] __attribute__((aligned(8)));
+};
+
 /**
  * @brief marshall_from_mar_votequorum_ring_id
  * @param dest

+ 63 - 0
include/corosync/votequorum.h

@@ -57,6 +57,7 @@ typedef uint64_t votequorum_handle_t;
 #define VOTEQUORUM_INFO_QDEVICE_ALIVE            128
 #define VOTEQUORUM_INFO_QDEVICE_CAST_VOTE        256
 #define VOTEQUORUM_INFO_QDEVICE_MASTER_WINS      512
+#define VOTEQUORUM_INFO_QDEVICE_HAS_EXTRA_INFO  1024
 
 #define VOTEQUORUM_QDEVICE_NODEID                0
 #define VOTEQUORUM_QDEVICE_MAX_NAME_LEN          255
@@ -67,6 +68,8 @@ typedef uint64_t votequorum_handle_t;
 #define VOTEQUORUM_NODESTATE_DEAD                2
 #define VOTEQUORUM_NODESTATE_LEAVING             3
 
+#define VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE 4096
+
 /** @} */
 
 /**
@@ -126,6 +129,20 @@ typedef void (*votequorum_expectedvotes_notification_fn_t) (
 	uint64_t context,
 	uint32_t expected_votes);
 
+/**
+ * @brief Callback to be notified of changes to a node's extra info
+ *
+ * @param node_id the node ID of the node that the extra info is associated with
+ * @param is_set true if the node has extra_info set, false otherwise
+ * @param extra_info pointer to the extra info for the sending node
+ */
+typedef void (*votequorum_qdevice_extra_info_fn_t) (
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t nodeid,
+	uint32_t ei_size,
+	void *extra_info);
+
 /**
  * @brief The votequorum_callbacks_t struct
  */
@@ -135,6 +152,28 @@ typedef struct {
 	votequorum_nodelist_notification_fn_t votequorum_nodelist_notify_fn;
 } votequorum_callbacks_t;
 
+typedef enum {
+	VOTEQUORUM_MODEL_V1 = 1,
+} votequorum_model_t;
+
+typedef struct {
+	votequorum_model_t model;
+	votequorum_quorum_notification_fn_t votequorum_quorum_notify_fn;
+	votequorum_expectedvotes_notification_fn_t votequorum_expectedvotes_notify_fn;
+	votequorum_nodelist_notification_fn_t votequorum_nodelist_notify_fn;
+	votequorum_qdevice_extra_info_fn_t votequorum_qdevice_extra_info_fn;
+} votequorum_model_v1_data_t;
+
+/**
+ * @brief Create a new quorum connection
+ * @param handle
+ * @param model
+ * @return
+ */
+cs_error_t votequorum_model_initialize (
+	votequorum_handle_t *handle,
+	const votequorum_model_v1_data_t *model);
+
 /**
  * @brief Create a new quorum connection
  * @param handle
@@ -311,6 +350,30 @@ cs_error_t votequorum_qdevice_master_wins (
 	const char *name,
 	unsigned int allow);
 
+/**
+ * @brief request the current extra_info for a node
+ * @param handle
+ * @param nodeid
+ * @param extra_info pointer to a buffer to fill. Should be of size VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE
+ * @return
+ */
+cs_error_t votequorum_get_qdevice_extra_info (
+	votequorum_handle_t handle,
+	unsigned int nodeid,
+	uint32_t *ei_size,
+	void *extra_info);
+
+/**
+ * @brief set the current extra_info for this node, should only be used by the qdevice
+ * @param handle
+ * @param extra_info pointer to the extra_info. Should be of size less than VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE
+ * @return
+ */
+cs_error_t votequorum_set_qdevice_extra_info (
+	votequorum_handle_t handle,
+	uint32_t ei_size,
+	const void *extra_info);
+
 #ifdef __cplusplus
 }
 #endif

+ 7 - 0
lib/libvotequorum.versions

@@ -19,3 +19,10 @@ COROSYNC_VOTEQUORUM_1.0 {
 		votequorum_context_get;
 		votequorum_context_set;
 };
+
+COROSYNC_VOTEQUORUM_1.1 {
+	global:
+		votequorum_model_initialize;
+		votequorum_get_qdevice_extra_info;
+		votequorum_set_qdevice_extra_info;
+} COROSYNC_VOTEQUORUM_1.0;

+ 1 - 1
lib/libvotequorum.verso

@@ -1 +1 @@
-8.0.0
+8.1.0

+ 142 - 6
lib/votequorum.c

@@ -38,6 +38,7 @@
 
 #include <config.h>
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -62,7 +63,7 @@ struct votequorum_inst {
 	qb_ipcc_connection_t *c;
 	int finalize;
 	void *context;
-	votequorum_callbacks_t callbacks;
+	votequorum_model_v1_data_t callbacks;
 };
 
 static void votequorum_inst_free (void *inst);
@@ -72,6 +73,20 @@ DECLARE_HDB_DATABASE(votequorum_handle_t_db, votequorum_inst_free);
 cs_error_t votequorum_initialize (
 	votequorum_handle_t *handle,
 	votequorum_callbacks_t *callbacks)
+{
+	votequorum_model_v1_data_t model = {
+		.model                              = VOTEQUORUM_MODEL_V1,
+		.votequorum_quorum_notify_fn        = callbacks->votequorum_quorum_notify_fn,
+		.votequorum_expectedvotes_notify_fn = callbacks->votequorum_expectedvotes_notify_fn,
+		.votequorum_nodelist_notify_fn      = callbacks->votequorum_nodelist_notify_fn,
+		.votequorum_qdevice_extra_info_fn   = NULL,
+	};
+    return votequorum_model_initialize (handle, &model);
+}
+
+cs_error_t votequorum_model_initialize (
+	votequorum_handle_t *handle,
+	const votequorum_model_v1_data_t *model)
 {
 	cs_error_t error;
 	struct votequorum_inst *votequorum_inst;
@@ -93,10 +108,10 @@ cs_error_t votequorum_initialize (
 		goto error_put_destroy;
 	}
 
-	if (callbacks)
-		memcpy(&votequorum_inst->callbacks, callbacks, sizeof (*callbacks));
+	if (model)
+		memcpy(&votequorum_inst->callbacks, model, sizeof (*model));
 	else
-		memset(&votequorum_inst->callbacks, 0, sizeof (*callbacks));
+		memset(&votequorum_inst->callbacks, 0, sizeof (*model));
 
 	hdb_handle_put (&votequorum_handle_t_db, *handle);
 
@@ -433,11 +448,12 @@ cs_error_t votequorum_dispatch (
 	cs_error_t error;
 	int cont = 1; /* always continue do loop except when set to 0 */
 	struct votequorum_inst *votequorum_inst;
-	votequorum_callbacks_t callbacks;
+	votequorum_model_v1_data_t callbacks;
 	struct qb_ipc_response_header *dispatch_data;
 	struct res_lib_votequorum_quorum_notification *res_lib_votequorum_quorum_notification;
 	struct res_lib_votequorum_nodelist_notification *res_lib_votequorum_nodelist_notification;
 	struct res_lib_votequorum_expectedvotes_notification *res_lib_votequorum_expectedvotes_notification;
+	struct res_lib_votequorum_qdevice_extra_info_notification *res_lib_votequorum_qdevice_extra_info_notification;
 	char dispatch_buf[IPC_DISPATCH_SIZE];
 	votequorum_ring_id_t ring_id;
 
@@ -497,7 +513,7 @@ cs_error_t votequorum_dispatch (
 		 * A risk of this dispatch method is that the callback routines may
 		 * operate at the same time that votequorum_finalize has been called in another thread.
 		 */
-		memcpy (&callbacks, &votequorum_inst->callbacks, sizeof (votequorum_callbacks_t));
+		memcpy (&callbacks, &votequorum_inst->callbacks, sizeof (votequorum_model_v1_data_t));
 
 		/*
 		 * Dispatch incoming message
@@ -542,6 +558,19 @@ cs_error_t votequorum_dispatch (
 								       res_lib_votequorum_expectedvotes_notification->expected_votes);
 			break;
 
+		case MESSAGE_RES_VOTEQUORUM_QDEVICE_EXTRA_INFO_NOTIFICATION:
+			if (callbacks.votequorum_qdevice_extra_info_fn == NULL) {
+				break;
+			}
+			res_lib_votequorum_qdevice_extra_info_notification = (struct res_lib_votequorum_qdevice_extra_info_notification *)dispatch_data;
+
+			callbacks.votequorum_qdevice_extra_info_fn(handle,
+			                                           res_lib_votequorum_qdevice_extra_info_notification->context,
+			                                           res_lib_votequorum_qdevice_extra_info_notification->nodeid,
+			                                           res_lib_votequorum_qdevice_extra_info_notification->ei_size,
+			                                           res_lib_votequorum_qdevice_extra_info_notification->extra_info);
+			break;
+
 		default:
 			error = CS_ERR_LIBRARY;
 			goto error_put;
@@ -809,3 +838,110 @@ error_exit:
 
 	return (error);
 }
+
+cs_error_t votequorum_get_qdevice_extra_info (
+	votequorum_handle_t handle,
+	unsigned int nodeid,
+	uint32_t *ei_size,
+	void *extra_info)
+{
+	if (!ei_size || !extra_info) {
+		return CS_ERR_INVALID_PARAM;
+	}
+	cs_error_t error;
+	struct votequorum_inst *inst;
+	struct iovec iov;
+	struct req_lib_votequorum_get_qdevice_extra_info req_lib_votequorum_get_qdevice_extra_info;
+
+	char buf[sizeof(struct res_lib_votequorum_get_qdevice_extra_info) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE];
+	struct res_lib_votequorum_get_qdevice_extra_info *res_lib_votequorum_get_qdevice_extra_info
+		= (struct res_lib_votequorum_get_qdevice_extra_info *)buf;
+
+	error = hdb_error_to_cs(hdb_handle_get (&votequorum_handle_t_db, handle, (void *)&inst));
+	if (error != CS_OK) {
+		return (error);
+	}
+
+	req_lib_votequorum_get_qdevice_extra_info.header.size = sizeof(req_lib_votequorum_get_qdevice_extra_info);
+	req_lib_votequorum_get_qdevice_extra_info.header.id = MESSAGE_REQ_VOTEQUORUM_QDEVICE_GET_EXTRA_INFO;
+	req_lib_votequorum_get_qdevice_extra_info.nodeid = nodeid;
+
+	iov.iov_base = (char *)&req_lib_votequorum_get_qdevice_extra_info;
+	iov.iov_len = sizeof (struct req_lib_votequorum_get_qdevice_extra_info);
+
+	error = qb_to_cs_error(qb_ipcc_sendv_recv (
+	                        inst->c,
+	                        &iov,
+	                        1,
+	                        buf,
+	                        sizeof (buf), CS_IPC_TIMEOUT_MS));
+
+	if (error != CS_OK) {
+		goto error_exit;
+	}
+
+	error = res_lib_votequorum_get_qdevice_extra_info->header.error;
+	*ei_size = res_lib_votequorum_get_qdevice_extra_info->ei_size;
+	memcpy(extra_info,
+	       res_lib_votequorum_get_qdevice_extra_info->extra_info,
+	       res_lib_votequorum_get_qdevice_extra_info->ei_size);
+
+error_exit:
+	hdb_handle_put (&votequorum_handle_t_db, handle);
+	return (error);
+}
+
+
+cs_error_t votequorum_set_qdevice_extra_info (
+	votequorum_handle_t handle,
+	uint32_t ei_size,
+	const void *extra_info)
+{
+	cs_error_t error;
+	struct votequorum_inst *inst;
+	struct iovec iov;
+	struct res_lib_votequorum_status res_lib_votequorum_status;
+	char buf[sizeof(struct req_lib_votequorum_set_qdevice_extra_info) + VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE];
+	struct req_lib_votequorum_set_qdevice_extra_info *req_lib_votequorum_set_qdevice_extra_info
+		= (struct req_lib_votequorum_set_qdevice_extra_info *)buf;
+
+	error = hdb_error_to_cs(hdb_handle_get (&votequorum_handle_t_db, handle, (void *)&inst));
+	if (error != CS_OK) {
+		return (error);
+	}
+
+	if(!extra_info) { // Force NULL to mean zero size
+		ei_size = 0;
+	}
+
+	if(ei_size > VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE) {
+		return CS_ERR_INVALID_PARAM;
+	}
+
+	req_lib_votequorum_set_qdevice_extra_info->header.size = sizeof(*req_lib_votequorum_set_qdevice_extra_info) + ei_size;
+	req_lib_votequorum_set_qdevice_extra_info->header.id = MESSAGE_REQ_VOTEQUORUM_QDEVICE_SET_EXTRA_INFO;
+	req_lib_votequorum_set_qdevice_extra_info->ei_size = ei_size;
+	if(extra_info) {
+		memcpy(req_lib_votequorum_set_qdevice_extra_info->extra_info, extra_info, ei_size);
+	}
+
+	iov.iov_base = buf;
+	iov.iov_len = req_lib_votequorum_set_qdevice_extra_info->header.size;
+
+	error = qb_to_cs_error(qb_ipcc_sendv_recv (
+		inst->c,
+		&iov,
+		1,
+		&res_lib_votequorum_status,
+		sizeof (struct res_lib_votequorum_status), CS_IPC_TIMEOUT_MS));
+
+	if (error != CS_OK) {
+		goto error_exit;
+	}
+
+	error = res_lib_votequorum_status.header.error;
+
+error_exit:
+	hdb_handle_put (&votequorum_handle_t_db, handle);
+	return (error);
+}

+ 3 - 0
man/Makefile.am

@@ -76,6 +76,7 @@ autogen_man		= cpg_context_get.3 \
 			  votequorum_finalize.3 \
 			  votequorum_getinfo.3 \
 			  votequorum_initialize.3 \
+			  votequorum_model_initialize.3 \
 			  votequorum_setexpected.3 \
 			  votequorum_setvotes.3 \
 			  votequorum_trackstart.3 \
@@ -85,6 +86,8 @@ autogen_man		= cpg_context_get.3 \
 			  votequorum_qdevice_update.3 \
 			  votequorum_qdevice_master_wins.3 \
 			  votequorum_qdevice_poll.3 \
+			  votequorum_get_qdevice_extra_info.3 \
+			  votequorum_set_qdevice_extra_info.3 \
 			  sam_data_getsize.3 \
 			  sam_data_restore.3 \
 			  sam_data_store.3 \

+ 3 - 0
man/corosync-quorumtool.8

@@ -82,6 +82,9 @@ just the first one.
 .B -f
 forcefully unregister a quorum device *DANGEROUS* *
 .TP
+.B -W <q|x>
+watch for update messages of a particular type: q quorum status, x extra info (votequorum only)
+.TP
 .B  -h (if no other argument)
 show this help text
 .TP

+ 67 - 0
man/votequorum_get_qdevice_extra_info.3.in

@@ -0,0 +1,67 @@
+.\"/*
+.\" * Copyright (c) 2026 IBM, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Thomas Jones <thomas.jones@ibm.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of the MontaVista Software, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH VOTEQUORUM_VOTES 3 @BUILDDATE@ "corosync Man Page" "Corosync Cluster Engine Programmer's Manual"
+.SH NAME
+votequorum_get_qdevice_extra_info \- Get the current extra_info for a node.
+.SH SYNOPSIS
+.B #include <corosync/votequorum.h>
+.sp
+.BI "cs_error_t votequorum_get_qdevice_extra_info(votequorum_handle_t " handle ", unsigned int " nodeid ", uint32_t * " ei_size ", const void * " extra_info ");"
+.SH DESCRIPTION
+The
+.B votequorum_get_qdevice_extra_info
+is used to get the
+.B extra_info
+stored by votequorum for the node with the specified id. The buffer should be of size
+.B VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE
+.SH RETURN VALUE
+This call returns the CS_OK value if successful, otherwise an error is returned.
+.PP
+.SH ERRORS
+@COMMONIPCERRORS@
+.SH "SEE ALSO"
+.BR votequorum_overview (3),
+.BR votequorum_initialize (3),
+.BR votequorum_finalize (3),
+.BR votequorum_getinfo (3),
+.BR votequorum_trackstart (3),
+.BR votequorum_trackstop (3),
+.BR votequorum_fd_get (3),
+.BR votequorum_dispatch (3),
+.BR votequorum_context_set (3),
+.BR votequorum_context_get (3),
+.BR votequorum_setexpected (3),
+.BR votequorum_set_qdevice_extra_info (3),
+.BR votequorum_set_qdevice_extra_info_notify (3)
+.PP

+ 1 - 0
man/votequorum_initialize.3.in

@@ -125,6 +125,7 @@ This call returns the CS_OK value if successful, otherwise an error is returned.
 .SH ERRORS
 @COMMONIPCERRORS@
 .SH "SEE ALSO"
+.BR votequorum_model_initialize (3),
 .BR votequorum_overview (3),
 .BR votequorum_finalize (3),
 .BR votequorum_getinfo (3),

+ 159 - 0
man/votequorum_model_initialize.3.in

@@ -0,0 +1,159 @@
+.\"/*
+.\" * Copyright (c) 2009,2012 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Christine Caulfield <ccaulfie@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of the MontaVista Software, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH VOTEQUORUM_MODEL_INITIALIZE 3 @BUILDDATE@ "corosync Man Page" "Corosync Cluster Engine Programmer's Manual"
+.SH NAME
+votequorum_model_initialize \- Create a new connection to the VoteQuorum service
+.SH SYNOPSIS
+.B #include <corosync/votequorum.h>
+.sp
+.BI "int votequorum_model_initialize(votequorum_handle_t *" handle ", const votequorum_model_v1_data_t *" model ");"
+.SH DESCRIPTION
+The
+.B votequorum_model_initialize
+function is used to initialize a connection to the vote-based quorum database API.
+.PP
+Each application may have several connections to the votequorum API.  Each application
+uses the
+.I handle
+argument to uniquely identify the connection.  The
+.I handle
+argument is then used in other function calls to identify the connection to be used
+for communication with the votequorum service.
+.PP
+Every time the voting configuration is about to change (eg a node joins or leave the cluster), the callback is called.
+The callback function is described by the following type definitions:
+
+.nf
+typedef void (*votequorum_nodelist_notification_fn_t) (
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t node_list_entries,
+	uint32_t node_list[]
+	);
+
+.fi
+
+Current ring_id (one get in votequorum_quorum_notification_fn) must be passed to
+.B votequorum_qdevice_poll
+to make qdevice voting valid.
+
+.PP
+Every time the quorum state changes (eg a node joins or leave the cluster), the callback is called.
+The callback function is described by the following type definitions:
+
+.nf
+typedef void (*votequorum_quorum_notification_fn_t) (
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t quorate,
+	uint32_t node_list_entries,
+	votequorum_node_t node_list[]
+	);
+
+.fi
+
+The difference between votequorum_nodelist_notification_t and votequorum_quorum_notification_t is subtle but important.
+The 'nodelist' callback is sent at the start of a cluster state transition and contains the new ring_id and only the list of
+nodes that are included in the sync state - ie only active nodes. No quorum information is included this callback
+because it is not available at that time.
+The 'quorum' callback is sent after the cluster state transition has completed and does contain quorum information.
+In addition, the nodelist contains a list of all nodes known to votequorum (whether up or down) and their state as well
+as information about the quorum device attached (if any). Quorum callbacks will not be sent for qdevice up and down
+events unless they affect quorum.
+
+.PP
+Every time the expected votes are changed, the callback is called.
+The expected votes callback function is described by the following type definitions:
+
+.nf
+typedef void (*votequorum_expectedvotes_notification_fn_t) (
+        votequorum_handle_t handle,
+        uint64_t context,
+        uint32_t expected_votes);
+.fi
+
+.PP
+Whenever a node's extra info is altered the callback is called.
+The extra info callback function is described by the following type definitions:
+
+.nf
+typedef void (*votequorum_qdevice_extra_info_fn_t) (
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t nodeid,
+	uint32_t ei_size,
+	void *extra_info);
+.fi
+.PP
+The
+.I model
+argument is of the type:
+
+.nf
+typedef enum {
+	VOTEQUORUM_MODEL_V1 = 1,
+} votequorum_model_t;
+
+typedef struct {
+	votequorum_model_t model;
+	votequorum_quorum_notification_fn_t votequorum_quorum_notify_fn;
+	votequorum_expectedvotes_notification_fn_t votequorum_expectedvotes_notify_fn;
+	votequorum_nodelist_notification_fn_t votequorum_nodelist_notify_fn;
+	votequorum_qdevice_extra_info_fn_t votequorum_qdevice_extra_info_fn;
+} votequorum_model_v1_data_t;
+.fi
+.PP
+When a configuration change occurs, the callback
+is called from the
+.B votequorum_dispatch()
+function.
+.PP
+.SH RETURN VALUE
+This call returns the CS_OK value if successful, otherwise an error is returned.
+.SH ERRORS
+@COMMONIPCERRORS@
+.SH "SEE ALSO"
+.BR votequorum_initialize (3),
+.BR votequorum_overview (3),
+.BR votequorum_finalize (3),
+.BR votequorum_getinfo (3),
+.BR votequorum_trackstart (3),
+.BR votequorum_trackstop (3),
+.BR votequorum_fd_get (3),
+.BR votequorum_dispatch (3),
+.BR votequorum_context_set (3),
+.BR votequorum_context_get (3),
+.BR votequorum_setexpected (3),
+.BR votequorum_setvotes (3)
+.PP

+ 70 - 0
man/votequorum_set_qdevice_extra_info.3.in

@@ -0,0 +1,70 @@
+.\"/*
+.\" * Copyright (c) 2026 IBM, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Thomas Jones <thomas.jones@ibm.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of the MontaVista Software, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH VOTEQUORUM_VOTES 3 @BUILDDATE@ "corosync Man Page" "Corosync Cluster Engine Programmer's Manual"
+.SH NAME
+votequorum_set_qdevice_extra_info \- Set the current extra_info for this node, should only be used by the qdevice.
+.SH SYNOPSIS
+.B #include <corosync/votequorum.h>
+.sp
+.BI "cs_error_t votequorum_set_qdevice_extra_info(votequorum_handle_t " handle ", uint32_t " ei_size ", const void * " extra_info ");"
+.SH DESCRIPTION
+The
+.B votequorum_set_qdevice_extra_info
+is used to set the
+.B extra_info
+stored by votequorum for the node on which the
+call is made. It is intended only for use by the currently active qdevice. The
+.B extra_info
+should start with 4 bytes of magic to identify the type of qdevice and
+format of the data. The data can be up to 4096 bytes, including the magic.
+.SH RETURN VALUE
+This call returns the CS_OK value if successful, otherwise an error is returned.
+.PP
+.SH ERRORS
+@COMMONIPCERRORS@
+.SH "SEE ALSO"
+.BR votequorum_overview (3),
+.BR votequorum_initialize (3),
+.BR votequorum_finalize (3),
+.BR votequorum_getinfo (3),
+.BR votequorum_trackstart (3),
+.BR votequorum_trackstop (3),
+.BR votequorum_fd_get (3),
+.BR votequorum_dispatch (3),
+.BR votequorum_context_set (3),
+.BR votequorum_context_get (3),
+.BR votequorum_setexpected (3),
+.BR votequorum_get_qdevice_extra_info (3),
+.BR votequorum_set_qdevice_extra_info_notify (3)
+.PP

+ 195 - 5
tools/corosync-quorumtool.c

@@ -35,6 +35,8 @@
 
 #include <config.h>
 
+#include <ctype.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -66,7 +68,9 @@ typedef enum {
 	CMD_SETVOTES,
 	CMD_SETEXPECTED,
 	CMD_MONITOR,
-	CMD_UNREGISTER_QDEVICE
+	CMD_WATCH_NOTIFICATIONS,
+	CMD_UNREGISTER_QDEVICE,
+	CMD_SET_EXTRA_INFO
 } command_t;
 
 typedef enum {
@@ -75,6 +79,12 @@ typedef enum {
 	SORT_NODENAME
 } sorttype_t;
 
+typedef enum {
+	WATCH_NONE       = 0,
+	WATCH_QUORUM     = (1 << 0),
+	WATCH_EXTRA_INFO = (1 << 1),
+} notification_watchtype_t;
+
 #define EXIT_NOT_QUORATE	2
 
 /*
@@ -122,6 +132,8 @@ static uint32_t g_called;
 static uint32_t g_vq_called;
 static uint32_t g_show_all_addrs = 0;
 
+static notification_watchtype_t g_watch_notifications = WATCH_NONE;
+
 /*
  * votequorum bits
  */
@@ -131,11 +143,19 @@ static void votequorum_notification_fn(
 	votequorum_ring_id_t ring_id,
 	uint32_t node_list_entries,
 	uint32_t node_list[]);
+static void votequorum_qdevice_extra_info_notification_fn(
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t nodeid,
+	uint32_t ei_size,
+	void *extra_info);
 static votequorum_handle_t v_handle;
-static votequorum_callbacks_t v_callbacks = {
+static votequorum_model_v1_data_t v_callbacks = {
+	.model = VOTEQUORUM_MODEL_V1,
 	.votequorum_quorum_notify_fn = NULL,
 	.votequorum_expectedvotes_notify_fn = NULL,
 	.votequorum_nodelist_notify_fn = votequorum_notification_fn,
+	.votequorum_qdevice_extra_info_fn = votequorum_qdevice_extra_info_notification_fn,
 };
 static uint32_t our_nodeid = 0;
 
@@ -171,6 +191,9 @@ static void show_usage(const char *name)
 	printf("  -i             show node IP addresses instead of the resolved name\n");
 	printf("  -o <a|n|i>     order by [a] IP address (default), [n] name, [i] nodeid\n");
 	printf("  -f             forcefully unregister a quorum device *DANGEROUS* (*)\n");
+	printf("  -W <q|x|qx>    watch for update messages. q quorum status, x extra info (votequorum only)\n");
+	printf("  -X <string>    set qdevice extra info on the node running the command (votequorum only)\n");
+	printf("  -C             clear qdevice extra info on the node running the command (votequorum only)\n");
 	printf("  -h             show this help text\n");
 	printf("  -V             show version and exit\n");
 	printf("\n");
@@ -381,6 +404,21 @@ static const char *node_name(uint32_t nodeid, name_format_t name_format)
 	return buf;
 }
 
+static void print_extra_info(uint32_t ei_size, const char *extra_info);
+
+static void votequorum_qdevice_extra_info_notification_fn(
+	votequorum_handle_t handle,
+	uint64_t context,
+	uint32_t nodeid,
+	uint32_t ei_size,
+	void *extra_info)
+{
+	if (g_watch_notifications & WATCH_EXTRA_INFO) {
+		printf(CS_PRI_NODE_ID_PADDED " ", nodeid);
+		print_extra_info(ei_size, extra_info);
+		printf("\n");
+	}
+}
 
 static void votequorum_notification_fn(
 	votequorum_handle_t handle,
@@ -417,6 +455,10 @@ static void quorum_notification_fn(
 			g_view_list[i].vq_info = NULL;
 		}
 	}
+
+	if(g_watch_notifications & WATCH_QUORUM) {
+		printf("%s\n", (quorate?"Quorate":"Not quorate"));
+	}
 }
 
 static void print_string_padded(const char *buf)
@@ -463,6 +505,31 @@ static int compare_nodenames(const void *one, const void *two)
       return strcmp(info1->name, info2->name);
 }
 
+static void print_extra_info(uint32_t ei_size, const char *extra_info)
+{
+	if (ei_size == 0) {
+		printf("<unset>");
+		return;
+	}
+
+	bool is_printable = extra_info[ei_size-1] == '\0';
+	for (uint32_t i = 0; i < ei_size - 1; i++) {
+		if(!isprint((unsigned char)extra_info[i])) {
+			is_printable = false;
+		}
+	}
+
+	if (is_printable) {
+		printf("%s", extra_info);
+		return;
+	}
+
+	printf("%" PRIu32 " bytes: ", ei_size);
+	for (uint32_t i = 0; i < ei_size; i++) {
+		printf("%02hhX ", (unsigned char)extra_info[i]);
+	}
+}
+
 static void display_nodes_data(nodeid_format_t nodeid_format, name_format_t name_format, sorttype_t sort_type)
 {
 	int i, display_qdevice = 0;
@@ -537,6 +604,17 @@ static void display_nodes_data(nodeid_format_t nodeid_format, name_format_t name
 			}
 		}
 		printf("%s", g_view_list[i].name);
+		if (display_qdevice
+			&& (info[i].flags & VOTEQUORUM_INFO_QDEVICE_REGISTERED)
+			    && (info[i].flags & VOTEQUORUM_INFO_QDEVICE_HAS_EXTRA_INFO)) {
+			uint32_t ei_size = 0;
+			char extra_info[VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE+1] = { 0 };
+			if(votequorum_get_qdevice_extra_info (v_handle, info[i].node_id, &ei_size, extra_info) == CS_OK) {
+				printf(" [");
+				print_extra_info(ei_size, extra_info);
+				printf("]");
+			}
+		}
 		if (g_view_list[i].node_id == our_nodeid) {
 			printf(" (local)");
 			if (v_handle) {
@@ -547,7 +625,7 @@ static void display_nodes_data(nodeid_format_t nodeid_format, name_format_t name
 	}
 
 	if (g_view_list_entries) {
-	        for (i=0; i < g_view_list_entries; i++) {
+	        for (i= 0; i < g_view_list_entries; i++) {
 		        free(g_view_list[i].name);
 		}
 		free(g_view_list);
@@ -760,6 +838,56 @@ quorum_err:
 	return EXIT_FAILURE;
 }
 
+static int watch_notifications(notification_watchtype_t watch) {
+	int err;
+
+	g_watch_notifications = watch;
+
+	if (q_type == QUORUM_FREE) {
+		printf("\nQuorum is not configured - cannot monitor\n");
+		return EXIT_FAILURE;
+	}
+
+	err=quorum_trackstart(q_handle, CS_TRACK_CHANGES);
+	if (err != CS_OK) {
+		fprintf(stderr, "Unable to start quorum status tracking: %s\n", cs_strerror(err));
+		goto quorum_err;
+	}
+
+	if (using_votequorum()) {
+		if ( (err=votequorum_trackstart(v_handle, 0LL, CS_TRACK_CHANGES)) != CS_OK) {
+			fprintf(stderr, "Unable to start votequorum status tracking: %s\n", cs_strerror(err));
+			goto quorum_err;
+		}
+	}
+
+	printf("Watching for updates:\n");
+
+	while (1) {
+		if (using_votequorum()) {
+			err = quorum_dispatch(q_handle, CS_DISPATCH_ALL);
+			if (err != CS_OK) {
+				fprintf(stderr, "Unable to dispatch quorum status: %s\n", cs_strerror(err));
+				goto quorum_err;
+			}
+			err = votequorum_dispatch(v_handle, CS_DISPATCH_ONE);
+			if (err != CS_OK) {
+				fprintf(stderr, "Unable to dispatch votequorum status: %s\n", cs_strerror(err));
+				goto quorum_err;
+			}
+		} else {
+			err = quorum_dispatch(q_handle, CS_DISPATCH_ONE);
+			if (err != CS_OK) {
+				fprintf(stderr, "Unable to dispatch quorum status: %s\n", cs_strerror(err));
+				goto quorum_err;
+			}
+		}
+	}
+
+quorum_err:
+	return EXIT_FAILURE;
+}
+
 static int show_nodes(nodeid_format_t nodeid_format, name_format_t name_format, sorttype_t sort_type)
 {
 	int err;
@@ -817,6 +945,24 @@ err_exit:
 	return result;
 }
 
+static int set_qdevice_extra_info(const char *extra_info)
+{
+	int err;
+
+	uint32_t ei_size = 0;
+	if (extra_info) {
+		ei_size = strlen(extra_info) + 1;
+	}
+
+	err = votequorum_set_qdevice_extra_info(v_handle, ei_size, extra_info);
+	if (err != CS_OK) {
+		fprintf(stderr, "Failed to set extra info %s\n", cs_strerror(err));
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
 /*
  * return -1 on error
  *         0 if OK
@@ -850,7 +996,7 @@ static int init_all(void) {
 		return 0;
 	}
 
-	if (votequorum_initialize(&v_handle, &v_callbacks) != CS_OK) {
+	if (votequorum_model_initialize(&v_handle, &v_callbacks) != CS_OK) {
 		fprintf(stderr, "Cannot initialise VOTEQUORUM service\n");
 		v_handle = 0;
 		goto out;
@@ -882,7 +1028,7 @@ static void close_all(void) {
 }
 
 int main (int argc, char *argv[]) {
-	const char *options = "VHaslpmfe:v:hin:o:";
+	const char *options = "VHaslpmfe:v:hin:o:W:X:C";
 	int opt;
 	int votes = 0;
 	int ret = 0;
@@ -892,7 +1038,9 @@ int main (int argc, char *argv[]) {
 	name_format_t address_format = ADDRESS_FORMAT_NAME;
 	command_t command_opt = CMD_SHOWSTATUS;
 	sorttype_t sort_opt = SORT_ADDR;
+	notification_watchtype_t watchtype = WATCH_NONE;
 	long long int l;
+	char *extra_info = NULL;
 
 	while ( (opt = getopt(argc, argv, options)) != -1 ) {
 		switch (opt) {
@@ -917,6 +1065,33 @@ int main (int argc, char *argv[]) {
 		case 'l':
 			command_opt = CMD_SHOWNODES;
 			break;
+		case 'W':
+			command_opt = CMD_WATCH_NOTIFICATIONS;
+			if (strchr(optarg, 'q')) {
+				watchtype |= WATCH_QUORUM;
+			}
+			if (strchr(optarg, 'x')) {
+				watchtype |= WATCH_EXTRA_INFO;
+			}
+			if(watchtype == WATCH_NONE) {
+				fprintf(stderr, "Invalid watch option. valid watch options are q(quorum) or x(qdevice extra info)\n");
+				exit(EXIT_FAILURE);
+			}
+			break;
+		case 'X':
+			command_opt = CMD_SET_EXTRA_INFO;
+			if (extra_info) {
+				free(extra_info);
+			}
+			extra_info = strdup(optarg);
+			break;
+		case 'C':
+			command_opt = CMD_SET_EXTRA_INFO;
+			if (extra_info) {
+				free(extra_info);
+			}
+			extra_info = NULL;
+			break;
 		case 'p':
 			machine_parsable = 1;
 			break;
@@ -1007,6 +1182,9 @@ int main (int argc, char *argv[]) {
 	case CMD_MONITOR:
 		ret = monitor_status(nodeid_format, address_format, sort_opt);
 		break;
+	case CMD_WATCH_NOTIFICATIONS:
+		ret = watch_notifications(watchtype);
+		break;
 	case CMD_UNREGISTER_QDEVICE:
 		if (using_votequorum() > 0) {
 			ret = unregister_qdevice();
@@ -1015,6 +1193,18 @@ int main (int argc, char *argv[]) {
 			ret = EXIT_FAILURE;
 		}
 		break;
+	case CMD_SET_EXTRA_INFO:
+		if (using_votequorum() > 0) {
+			ret = set_qdevice_extra_info(extra_info);
+		} else {
+			fprintf(stderr, "You cannot set quorum device extra info, corosync is not using votequorum\n");
+			ret = EXIT_FAILURE;
+		}
+		break;
+	}
+
+	if (extra_info) {
+		free(extra_info);
 	}
 
 	close_all();