1    	/*
2    	 * Copyright (c) 2026 IBM, Inc.
3    	 *
4    	 * All rights reserved.
5    	 *
6    	 * Authors: Thomas Jones <thomas.jones@ibm.com>
7    	 *
8    	 * This software licensed under BSD license, the text of which follows:
9    	 *
10   	 * Redistribution and use in source and binary forms, with or without
11   	 * modification, are permitted provided that the following conditions are met:
12   	 *
13   	 * - Redistributions of source code must retain the above copyright notice,
14   	 *   this list of conditions and the following disclaimer.
15   	 * - Redistributions in binary form must reproduce the above copyright notice,
16   	 *   this list of conditions and the following disclaimer in the documentation
17   	 *   and/or other materials provided with the distribution.
18   	 * - Neither the name of the Red Hat Inc. nor the names of its
19   	 *   contributors may be used to endorse or promote products derived from this
20   	 *   software without specific prior written permission.
21   	 *
22   	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23   	 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24   	 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25   	 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26   	 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27   	 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28   	 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29   	 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30   	 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31   	 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
32   	 * THE POSSIBILITY OF SUCH DAMAGE.
33   	 */
34   	
35   	#include <config.h>
36   	
37   	#include <ctype.h>
38   	#include <unistd.h>
39   	#include <inttypes.h>
40   	#include <stdio.h>
41   	#include <stdlib.h>
42   	#include <stdbool.h>
43   	#include <string.h>
44   	#include <sys/types.h>
45   	#include <sys/socket.h>
46   	#include <netdb.h>
47   	#include <limits.h>
48   	
49   	#include <corosync/totem/totem.h>
50   	#include <corosync/cfg.h>
51   	#include <corosync/cmap.h>
52   	#include <corosync/quorum.h>
53   	#include <corosync/votequorum.h>
54   	#include "util.h"
55   	
56   	typedef enum {
57   		CMD_SHOWSTATUS,
58   		CMD_SETINFO,
59   		CMD_CLEARINFO,
60   		CMD_MONITOR
61   	} command_t;
62   	
63   	static void show_usage(const char *name)
64   	{
65   		printf("usage: \n");
66   		printf("%s <options>\n", name);
67   		printf("\n");
68   		printf("  options:\n");
69   		printf("\n");
70   		printf("  -s             show current extra info as strings\n");
71   		printf("  -m             constantly monitor for updates\n");
72   		printf("  -S <info>      set the extra info on the node running the command\n");
73   		printf("  -c             clear the extra info on the node running the command\n");
74   		printf("  -h             show this help text\n");
75   		printf("  -V             show version and exit\n");
76   		printf("\n");
77   	}
78   	
79   	static cmap_handle_t cmap_handle;
80   	
81   	/*
82   	 * votequorum bits
83   	 */
84   	static void vq_qdevice_extra_info_fn(
85   		votequorum_handle_t handle,
86   		uint64_t context,
87   		uint32_t nodeid,
88   		uint32_t ei_size,
89   		void *extra_info);
90   	static void vq_quorum_notification_fn_t (
91   		votequorum_handle_t handle,
92   		uint64_t context,
93   		uint32_t quorate,
94   		uint32_t node_list_entries,
95   		votequorum_node_t node_list[]);
96   	static void vq_notification_fn(
97   		votequorum_handle_t handle,
98   		uint64_t context,
99   		votequorum_ring_id_t ring_id,
100  		uint32_t node_list_entries,
101  		uint32_t node_list[]);
102  	
103  	static votequorum_handle_t v_handle;
104  	static votequorum_model_v1_data_t v_callbacks = {
105  		.model = VOTEQUORUM_MODEL_V1,
106  		.votequorum_quorum_notify_fn = vq_quorum_notification_fn_t,
107  		.votequorum_expectedvotes_notify_fn = NULL,
108  		.votequorum_nodelist_notify_fn = vq_notification_fn,
109  		.votequorum_qdevice_extra_info_fn = vq_qdevice_extra_info_fn,
110  	};
111  	
112  	static bool g_quorate = false;
113  	static bool g_vq_called = false;
114  	static bool g_vq_quorum_called = false;
115  	static uint32_t g_node_list_entries = 0;
116  	static uint32_t *g_node_list = NULL;
117  	
118  	static uint32_t g_vqnode_list_entries = 0;
119  	static votequorum_node_t *g_vqnode_list = NULL;
120  	
121  	static void vq_notification_fn(
122  		votequorum_handle_t handle,
123  		uint64_t context,
124  		votequorum_ring_id_t ring_id,
125  		uint32_t node_list_entries,
126  		uint32_t node_list[])
127  	{
128  		(void)handle; (void)context; (void)ring_id;
129  		if(g_node_list) {
130  			free(g_node_list);
131  		}
132  	
133  		g_node_list_entries = node_list_entries;
134  		g_node_list = malloc(sizeof(*node_list) * node_list_entries);
135  		memcpy(g_node_list, node_list, sizeof(*node_list) * node_list_entries);
136  	
137  		g_vq_called = true;
138  	}
139  	
140  	static void vq_quorum_notification_fn_t (
141  		votequorum_handle_t handle,
142  		uint64_t context,
143  		uint32_t quorate,
144  		uint32_t node_list_entries,
145  		votequorum_node_t node_list[])
146  	{
147  		if(g_vqnode_list) {
148  			free(g_vqnode_list);
149  		}
150  		g_vqnode_list_entries = node_list_entries;
151  		g_vqnode_list = malloc(sizeof(*node_list) * node_list_entries);
152  		memcpy(g_vqnode_list, node_list, sizeof(*node_list) * node_list_entries);
153  	
154  		g_quorate = quorate;
155  	
156  		g_vq_quorum_called = true;
157  	}
158  	
159  	static int get_quorum_type(char *quorum_type, size_t quorum_type_len)
160  	{
161  		int err;
162  		char *str = NULL;
163  	
164  		if ((quorum_type == NULL) || (quorum_type_len <= 0)) {
165  			return -1;
166  		}
167  	
168  		err = cmap_get_string(cmap_handle, "quorum.provider", &str);
169  		if (err != CS_OK) {
170  			goto out;
171  		}
172  	
173  		if (str == NULL) {
174  			return -1;
175  		}
176  	
177  		strncpy(quorum_type, str, quorum_type_len - 1);
178  		free(str);
179  	
180  		return 0;
181  	out:
182  		return err;
183  	}
184  	
185  	/*
186  	 * Returns 1 if 'votequorum' is active. The called then knows that
187  	 * votequorum calls should work and can provide extra information
188  	 */
189  	static int using_votequorum(void)
190  	{
191  		char quorumtype[256];
192  		int using_voteq;
193  	
194  		memset(quorumtype, 0, sizeof(quorumtype));
195  	
196  		if (get_quorum_type(quorumtype, sizeof(quorumtype))) {
197  			return -1;
198  		}
199  	
200  		if (strcmp(quorumtype, "corosync_votequorum") == 0) {
201  			using_voteq = 1;
202  		} else {
203  			using_voteq = 0;
204  		}
205  	
206  		return using_voteq;
207  	}
208  	
209  	static int init_vq_info(void)
210  	{
211  		int err;
212  	
213  		err = votequorum_trackstart(v_handle, 0LL, CS_TRACK_CURRENT);
214  		if (err != CS_OK) {
215  			fprintf(stderr, "Unable to start votequorum status tracking: %s\n", cs_strerror(err));
216  			goto exit;
217  		}
218  	
219  		g_vq_called = false;
220  		g_vq_quorum_called = false;
221  		while ((!g_vq_called || !g_vq_quorum_called) && err == CS_OK) {
222  			err = votequorum_dispatch(v_handle, CS_DISPATCH_ONE);
223  			if (err != CS_OK) {
224  				fprintf(stderr, "Unable to dispatch votequorum status: %s\n", cs_strerror(err));
225  				goto exit;
226  			}
227  		}
228  	
229  	exit:
230  		if (err != CS_OK) {
231  			return EXIT_FAILURE;
232  		}
233  		return EXIT_SUCCESS;
234  	}
235  	
236  	static void print_extra_info(uint32_t ei_size, const char *extra_info)
237  	{
238  		if (ei_size == 0) {
239  			printf("<unset>");
240  			return;
241  		}
242  	
243  		if (strncmp(extra_info, "Qdsk", 4) == 0) {
244  			printf("<Qdisk> key:%s", extra_info+5);
245  			return;
246  		}
247  	
248  		bool is_printable = extra_info[ei_size-1] == '\0';
249  		for (uint32_t i = 0; i < ei_size - 1; i++) {
250  			if(!isprint((unsigned char)extra_info[i])) {
251  				is_printable = false;
252  			}
253  		}
254  		if (is_printable) {
255  			printf("<unknown> %s", extra_info);
256  			return;
257  		}
258  	
259  		printf("<unknown> %" PRIu32 " bytes: ", ei_size);
260  		for (uint32_t i = 0; i < ei_size; i++) {
261  			printf("%02hhX ", (unsigned char)extra_info[i]);
262  		}
263  	}
264  	
265  	static int show_status(void)
266  	{
267  		int err = CS_OK;
268  	
269  		if (init_vq_info()) {
270  			return EXIT_FAILURE;
271  		}
272  	
273  		for (uint32_t i = 0; i < g_vqnode_list_entries; i++) {
274  			uint32_t ei_size = 0;
275  			char buf[VOTEQUORUM_QDEVICE_EXTRA_NODEINFO_MAXSIZE+1] = { 0 };
276  			err = votequorum_get_qdevice_extra_info (v_handle, g_vqnode_list[i].nodeid, &ei_size, buf);
277  	
278  			printf(CS_PRI_NODE_ID_PADDED " ", g_vqnode_list[i].nodeid);
279  			if (err == CS_OK && ei_size) {
280  				print_extra_info(ei_size, buf);
281  				printf("\n");
282  			} else {
283  				printf("<unset>\n");
284  			}
285  		}
286  	
287  		if (err != CS_OK) {
288  			return EXIT_FAILURE;
289  		}
290  	
291  		return EXIT_SUCCESS;
292  	}
293  	
294  	bool g_monitor_output = false;
295  	
296  	static void vq_qdevice_extra_info_fn(
297  		votequorum_handle_t handle,
298  		uint64_t context,
299  		uint32_t nodeid,
300  		uint32_t ei_size,
301  		void *extra_info)
302  	{
303  		if (g_monitor_output) {
304  			printf(CS_PRI_NODE_ID_PADDED " ", nodeid);
305  			print_extra_info(ei_size, extra_info);
306  			printf("\n");
307  		}
308  	}
309  	
310  	static int monitor_status(void)
311  	{
312  		int err;
313  	
314  		printf("Watching for updates:\n");
315  	
316  		g_monitor_output = true;
317  	
318  		err = votequorum_trackstart(v_handle, UINT64_C(0), CS_TRACK_CURRENT | CS_TRACK_CHANGES);
(1) Event cond_cannot_single: Condition "err != CS_OK", taking true branch. Now the value of "err" cannot be equal to 1.
Also see events: [cond_cannot_single][cannot_single][dead_error_condition][dead_error_line]
319  		if (err != CS_OK) {
320  			fprintf(stderr, "Unable to start votequorum status tracking: %s\n", cs_strerror(err));
321  			goto exit;
322  		}
323  	
324  		while (1) {
325  			err = votequorum_dispatch(v_handle, CS_DISPATCH_ONE);
(2) Event cond_cannot_single: Condition "err != CS_OK", taking true branch. Now the value of "err" cannot be equal to 1.
Also see events: [cond_cannot_single][cannot_single][dead_error_condition][dead_error_line]
326  			if (err != CS_OK) {
327  				fprintf(stderr, "Unable to dispatch votequorum status: %s\n", cs_strerror(err));
328  				goto exit;
329  			}
330  		}
331  	
332  	exit:
(3) Event cannot_single: At condition "err != CS_OK", the value of "err" cannot be equal to 1.
(4) Event dead_error_condition: The condition "err != CS_OK" must be true.
Also see events: [cond_cannot_single][cond_cannot_single][dead_error_line]
333  		if (err != CS_OK) {
334  			return EXIT_FAILURE;
335  		}
336  	
CID (unavailable; MK=178a03c5dceb081652adbb7e015d60e9) (#1 of 1): Logically dead code (DEADCODE):
(5) Event dead_error_line: Execution cannot reach this statement: "return 0;".
Also see events: [cond_cannot_single][cond_cannot_single][cannot_single][dead_error_condition]
337  		return EXIT_SUCCESS;
338  	}
339  	
340  	static int set_info(const char *extra_info)
341  	{
342  		int err;
343  	
344  		if (init_vq_info()) {
345  			return EXIT_FAILURE;
346  		}
347  	
348  		uint32_t ei_size = 0;
349  		if (extra_info) {
350  			ei_size = strlen(extra_info) + 1;
351  		}
352  	
353  		err = votequorum_set_qdevice_extra_info(v_handle, ei_size, extra_info);
354  		if (err != CS_OK) {
355  			fprintf(stderr, "Failed to set extra info %s\n", cs_strerror(err));
356  			return EXIT_FAILURE;
357  		}
358  	
359  		return EXIT_SUCCESS;
360  	}
361  	
362  	static int init_all(void)
363  	{
364  		if (cmap_initialize(&cmap_handle) != CS_OK) {
365  			fprintf(stderr, "Cannot initialize CMAP service\n");
366  			cmap_handle = 0;
367  			goto out;
368  		}
369  	
370  		if (using_votequorum() <= 0) {
371  			goto out;
372  		}
373  	
374  		if (votequorum_model_initialize(&v_handle, &v_callbacks) != CS_OK) {
375  			fprintf(stderr, "Cannot initialise VOTEQUORUM service\n");
376  			v_handle = 0;
377  			goto out;
378  		}
379  	
380  		return 0;
381  	out:
382  		return -1;
383  	}
384  	
385  	static void close_all(void) {
386  		if (cmap_handle) {
387  			cmap_finalize(cmap_handle);
388  		}
389  		if (v_handle) {
390  			votequorum_finalize(v_handle);
391  		}
392  	}
393  	
394  	int main(int argc, char *argv[])
395  	{
396  		const char *options = "VhsmcS:";
397  		int opt;
398  		int ret = 0;
399  		command_t command_opt = CMD_SHOWSTATUS;
400  		const char *extra_info = NULL;
401  	
402  		while ( (opt = getopt(argc, argv, options)) != -1 ) {
403  			switch (opt) {
404  			case 's':
405  				command_opt = CMD_SHOWSTATUS;
406  				break;
407  			case 'm':
408  				command_opt = CMD_MONITOR;
409  				break;
410  			case 'c':
411  				command_opt = CMD_CLEARINFO;
412  				break;
413  			case 'S':
414  				command_opt = CMD_SETINFO;
415  				extra_info = strdup(optarg);
416  				break;
417  			case 'V':
418  				printf("corosync-qdeitool version: %s\n", VERSION);
419  				exit(EXIT_SUCCESS);
420  				break;
421  			case 'h':
422  				show_usage(argv[0]);
423  				exit(EXIT_SUCCESS);
424  				break;
425  			case ':':
426  			case '?':
427  			default:
428  				show_usage(argv[0]);
429  				exit(EXIT_FAILURE);
430  				break;
431  			}
432  		}
433  	
434  		if (init_all()) {
435  			close_all();
436  			exit(EXIT_FAILURE);
437  		}
438  	
439  		switch (command_opt) {
440  			case CMD_SHOWSTATUS:
441  				ret = show_status();
442  				break;
443  			case CMD_CLEARINFO:
444  			case CMD_SETINFO:
445  				ret = set_info(extra_info);
446  				break;
447  			case CMD_MONITOR:
448  				ret = monitor_status();
449  				break;
450  			default:
451  				break;
452  		}
453  	
454  		close_all();
455  		return ret;
456  	}
457