1 /*
2 * Copyright 2004-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <stdbool.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <inttypes.h>
18 #include <sys/types.h>
19
20 #include <glib.h>
21 #include <libxml/tree.h> // xmlNode
22 #include <libxml/xpath.h> // xmlXPathObject, etc.
23
24 #include <crm/crm.h>
25 #include <crm/stonith-ng.h>
26 #include <crm/fencing/internal.h>
27 #include <crm/common/xml.h>
28
29 #include <crm/common/mainloop.h>
30
31 #include "fencing_private.h"
32
33 // Used as stonith_t:st_private
34 typedef struct {
35 char *token;
36 crm_ipc_t *ipc;
37 mainloop_io_t *source;
38 GHashTable *stonith_op_callback_table;
39 GList *notify_list;
40 int notify_refcnt;
41 bool notify_deletes;
42
43 void (*op_callback) (stonith_t * st, stonith_callback_data_t * data);
44
45 } stonith_private_t;
46
47 // Used as stonith_event_t:opaque
48 struct event_private {
49 pcmk__action_result_t result;
50 };
51
52 typedef struct {
53 const char *event;
54 const char *obj_id; /* implement one day */
55 const char *obj_type; /* implement one day */
56 void (*notify) (stonith_t * st, stonith_event_t * e);
57 bool delete;
58
59 } stonith_notify_client_t;
60
61 typedef struct {
62 void (*callback) (stonith_t * st, stonith_callback_data_t * data);
63 const char *id;
64 void *user_data;
65 gboolean only_success;
66 gboolean allow_timeout_updates;
67 struct timer_rec_s *timer;
68
69 } stonith_callback_client_t;
70
71 struct notify_blob_s {
72 stonith_t *stonith;
73 xmlNode *xml;
74 };
75
76 struct timer_rec_s {
77 int call_id;
78 int timeout;
79 guint ref;
80 stonith_t *stonith;
81 };
82
83 typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *,
84 xmlNode *, xmlNode *, xmlNode **, xmlNode **);
85
86 xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
87 int call_options);
88 static int stonith_send_command(stonith_t *stonith, const char *op,
89 xmlNode *data, xmlNode **output_data,
90 int call_options, int timeout);
91
92 static void stonith_connection_destroy(gpointer user_data);
93 static void stonith_send_notification(gpointer data, gpointer user_data);
94 static int stonith_api_del_notification(stonith_t *stonith,
95 const char *event);
96
97 /*!
98 * \internal
99 * \brief Parse fence agent namespace from a string
100 *
101 * \param[in] namespace_s Name of namespace as string
102 *
103 * \return enum value parsed from \p namespace_s
104 */
105 static enum stonith_namespace
106 parse_namespace(const char *namespace_s)
107 {
108 if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) {
109 return st_namespace_any;
110 }
111 /* @TODO Is "redhat" still necessary except for stonith_text2namespace()
112 * backward compatibility?
113 */
114 if (pcmk__str_any_of(namespace_s, "redhat", "stonith-ng", NULL)) {
115 return st_namespace_rhcs;
116 }
117 if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) {
118 return st_namespace_internal;
119 }
120 if (pcmk__str_eq(namespace_s, "heartbeat", pcmk__str_none)) {
121 return st_namespace_lha;
122 }
123 return st_namespace_invalid;
124 }
125
126 /*!
127 * \internal
128 * \brief Get name of a fence agent namespace as a string
129 *
130 * \param[in] st_namespace Namespace as enum value
131 *
132 * \return Name of \p st_namespace as a string
133 */
134 static const char *
135 namespace_text(enum stonith_namespace st_namespace)
136 {
137 switch (st_namespace) {
138 case st_namespace_any:
139 return "any";
140 case st_namespace_rhcs:
141 return "stonith-ng";
142 case st_namespace_internal:
143 return "internal";
144 case st_namespace_lha:
145 return "heartbeat";
146 default:
147 return "unsupported";
148 }
149 }
150
151 /*!
152 * \internal
153 * \brief Determine fence agent namespace from agent name
154 *
155 * This involves external checks (for example, checking the existence of a file
156 * or calling an external library function).
157 *
158 * \param[in] agent Fence agent name
159 *
160 * \return Namespace to which \p agent belongs, or \c st_namespace_invalid if
161 * not found
162 */
163 static enum stonith_namespace
164 get_namespace_from_agent(const char *agent)
165 {
166 if (stonith__agent_is_rhcs(agent)) {
167 return st_namespace_rhcs;
168 }
169
170 #if HAVE_STONITH_STONITH_H
171 if (stonith__agent_is_lha(agent)) {
172 return st_namespace_lha;
173 }
174 #endif // HAVE_STONITH_STONITH_H
175
176 return st_namespace_invalid;
177 }
178
179 gboolean
180 stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node)
181 {
182 gboolean rv = FALSE;
183 stonith_t *stonith_api = (st != NULL)? st : stonith__api_new();
184 char *list = NULL;
185
186 if(stonith_api) {
187 if (stonith_api->state == stonith_disconnected) {
188 int rc = stonith_api->cmds->connect(stonith_api, "stonith-api", NULL);
189
190 if (rc != pcmk_ok) {
191 pcmk__err("Failed connecting to Stonith-API for "
192 "watchdog-fencing-query");
193 }
194 }
195
196 if (stonith_api->state != stonith_disconnected) {
197 /* caveat!!!
198 * this might fail when the fencer is just updating the device-list
199 * probably something we should fix as well for other api-calls */
200 int rc = stonith_api->cmds->list(stonith_api, st_opt_sync_call, STONITH_WATCHDOG_ID, &list, 0);
201 if ((rc != pcmk_ok) || (list == NULL)) {
202 /* due to the race described above it can happen that
203 * we drop in here - so as not to make remote nodes
204 * panic on that answer
205 */
206 if (rc == -ENODEV) {
207 pcmk__notice("Cluster does not have watchdog fencing "
208 "device");
209 } else {
210 pcmk__warn("Could not check for watchdog fencing device: %s",
211 pcmk_strerror(rc));
212 }
213 } else if (list[0] == '\0') {
214 rv = TRUE;
215 } else {
216 GList *targets = stonith__parse_targets(list);
217 rv = pcmk__str_in_list(node, targets, pcmk__str_casei);
218 g_list_free_full(targets, free);
219 }
220 free(list);
221 if (!st) {
222 /* if we're provided the api we still might have done the
223 * connection - but let's assume the caller won't bother
224 */
225 stonith_api->cmds->disconnect(stonith_api);
226 }
227 }
228
229 if (!st) {
230 stonith__api_free(stonith_api);
231 }
232 } else {
233 pcmk__err("Stonith-API for watchdog-fencing-query couldn't be created");
234 }
235 pcmk__trace("Pacemaker assumes node %s %sto do watchdog-fencing", node,
236 (rv? "" : "not "));
237 return rv;
238 }
239
240 gboolean
241 stonith__watchdog_fencing_enabled_for_node(const char *node)
242 {
243 return stonith__watchdog_fencing_enabled_for_node_api(NULL, node);
244 }
245
246 /* when cycling through the list we don't want to delete items
247 so just mark them and when we know nobody is using the list
248 loop over it to remove the marked items
249 */
250 static void
251 foreach_notify_entry (stonith_private_t *private,
252 GFunc func,
253 gpointer user_data)
254 {
255 private->notify_refcnt++;
256 g_list_foreach(private->notify_list, func, user_data);
257 private->notify_refcnt--;
258 if ((private->notify_refcnt == 0) &&
259 private->notify_deletes) {
260 GList *list_item = private->notify_list;
261
262 private->notify_deletes = FALSE;
263 while (list_item != NULL)
264 {
265 stonith_notify_client_t *list_client = list_item->data;
266 GList *next = g_list_next(list_item);
267
268 if (list_client->delete) {
269 free(list_client);
270 private->notify_list =
271 g_list_delete_link(private->notify_list, list_item);
272 }
273 list_item = next;
274 }
275 }
276 }
277
278 static void
279 stonith_connection_destroy(gpointer user_data)
280 {
281 stonith_t *stonith = user_data;
282 stonith_private_t *native = NULL;
283 struct notify_blob_s blob;
284
285 pcmk__trace("Sending destroyed notification");
286 blob.stonith = stonith;
287 blob.xml = pcmk__xe_create(NULL, PCMK__XE_NOTIFY);
288
289 native = stonith->st_private;
290 native->ipc = NULL;
291 native->source = NULL;
292
293 g_clear_pointer(&native->token, free);
294 stonith->state = stonith_disconnected;
295 pcmk__xe_set(blob.xml, PCMK__XA_T, PCMK__VALUE_ST_NOTIFY);
296 pcmk__xe_set(blob.xml, PCMK__XA_SUBT, PCMK__VALUE_ST_NOTIFY_DISCONNECT);
297
298 foreach_notify_entry(native, stonith_send_notification, &blob);
299 pcmk__xml_free(blob.xml);
300 }
301
302 xmlNode *
303 create_device_registration_xml(const char *id, enum stonith_namespace standard,
304 const char *agent,
305 const stonith_key_value_t *params,
306 const char *rsc_provides)
307 {
308 xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
309 xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
310
311 #if HAVE_STONITH_STONITH_H
312 if (standard == st_namespace_any) {
313 standard = get_namespace_from_agent(agent);
314 }
315 if (standard == st_namespace_lha) {
316 hash2field((gpointer) "plugin", (gpointer) agent, args);
317 agent = "fence_legacy";
318 }
319 #endif
320
321 pcmk__xe_set(data, PCMK_XA_ID, id);
322 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
323 pcmk__xe_set(data, PCMK_XA_AGENT, agent);
324 if ((standard != st_namespace_any) && (standard != st_namespace_invalid)) {
325 pcmk__xe_set(data, PCMK__XA_NAMESPACE, namespace_text(standard));
326 }
327 if (rsc_provides) {
328 pcmk__xe_set(data, PCMK__XA_RSC_PROVIDES, rsc_provides);
329 }
330
331 for (; params; params = params->next) {
332 hash2field((gpointer) params->key, (gpointer) params->value, args);
333 }
334
335 return data;
336 }
337
338 static int
339 stonith_api_register_device(stonith_t *st, int call_options,
340 const char *id, const char *namespace_s,
341 const char *agent,
342 const stonith_key_value_t *params)
343 {
344 int rc = 0;
345 xmlNode *data = NULL;
346
347 data = create_device_registration_xml(id, parse_namespace(namespace_s),
348 agent, params, NULL);
349
350 rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0);
351 pcmk__xml_free(data);
352
353 return rc;
354 }
355
356 static int
357 stonith_api_remove_device(stonith_t * st, int call_options, const char *name)
358 {
359 int rc = 0;
360 xmlNode *data = NULL;
361
362 data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
363 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
364 pcmk__xe_set(data, PCMK_XA_ID, name);
365 rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0);
366 pcmk__xml_free(data);
367
368 return rc;
369 }
370
371 static int
372 stonith_api_remove_level_full(stonith_t *st, int options,
373 const char *node, const char *pattern,
374 const char *attr, const char *value, int level)
375 {
376 int rc = 0;
377 xmlNode *data = NULL;
378
379 CRM_CHECK(node || pattern || (attr && value), return -EINVAL);
380
381 data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
382 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
383
384 if (node) {
385 pcmk__xe_set(data, PCMK_XA_TARGET, node);
386
387 } else if (pattern) {
388 pcmk__xe_set(data, PCMK_XA_TARGET_PATTERN, pattern);
389
390 } else {
391 pcmk__xe_set(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
392 pcmk__xe_set(data, PCMK_XA_TARGET_VALUE, value);
393 }
394
395 pcmk__xe_set_int(data, PCMK_XA_INDEX, level);
396 rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0);
397 pcmk__xml_free(data);
398
399 return rc;
400 }
401
402 static int
403 stonith_api_remove_level(stonith_t * st, int options, const char *node, int level)
404 {
405 return stonith_api_remove_level_full(st, options, node,
406 NULL, NULL, NULL, level);
407 }
408
409 /*!
410 * \internal
411 * \brief Create XML for fence topology level registration request
412 *
413 * \param[in] node If not NULL, target level by this node name
414 * \param[in] pattern If not NULL, target by node name using this regex
415 * \param[in] attr If not NULL, target by this node attribute
416 * \param[in] value If not NULL, target by this node attribute value
417 * \param[in] level Index number of level to register
418 * \param[in] device_list List of devices in level
419 *
420 * \return Newly allocated XML tree on success, NULL otherwise
421 *
422 * \note The caller should set only one of node, pattern or attr/value.
423 */
424 xmlNode *
425 create_level_registration_xml(const char *node, const char *pattern,
426 const char *attr, const char *value,
427 int level, const stonith_key_value_t *device_list)
428 {
429 GString *list = NULL;
430 xmlNode *data;
431
432 CRM_CHECK(node || pattern || (attr && value), return NULL);
433
434 data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
435
436 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
437 pcmk__xe_set_int(data, PCMK_XA_ID, level);
438 pcmk__xe_set_int(data, PCMK_XA_INDEX, level);
439
440 if (node) {
441 pcmk__xe_set(data, PCMK_XA_TARGET, node);
442
443 } else if (pattern) {
444 pcmk__xe_set(data, PCMK_XA_TARGET_PATTERN, pattern);
445
446 } else {
447 pcmk__xe_set(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
448 pcmk__xe_set(data, PCMK_XA_TARGET_VALUE, value);
449 }
450
451 for (; device_list; device_list = device_list->next) {
452 pcmk__add_separated_word(&list, 1024, device_list->value, ",");
453 }
454
455 if (list != NULL) {
456 pcmk__xe_set(data, PCMK_XA_DEVICES, (const char *) list->str);
457 g_string_free(list, TRUE);
458 }
459 return data;
460 }
461
462 static int
463 stonith_api_register_level_full(stonith_t *st, int options, const char *node,
464 const char *pattern, const char *attr,
465 const char *value, int level,
466 const stonith_key_value_t *device_list)
467 {
468 int rc = 0;
469 xmlNode *data = create_level_registration_xml(node, pattern, attr, value,
470 level, device_list);
471 CRM_CHECK(data != NULL, return -EINVAL);
472
473 rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0);
474 pcmk__xml_free(data);
475
476 return rc;
477 }
478
479 static int
480 stonith_api_register_level(stonith_t * st, int options, const char *node, int level,
481 const stonith_key_value_t * device_list)
482 {
483 return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL,
484 level, device_list);
485 }
486
487 static int
488 stonith_api_device_list(stonith_t *stonith, int call_options,
489 const char *namespace_s, stonith_key_value_t **devices,
490 int timeout)
491 {
492 int count = 0;
493 enum stonith_namespace ns = parse_namespace(namespace_s);
494
495 if (devices == NULL) {
496 pcmk__err("Parameter error: stonith_api_device_list");
497 return -EFAULT;
498 }
499
500 #if HAVE_STONITH_STONITH_H
501 // Include Linux-HA agents if requested
502 if ((ns == st_namespace_any) || (ns == st_namespace_lha)) {
503 count += stonith__list_lha_agents(devices);
504 }
505 #endif
506
507 // Include Red Hat agents if requested
508 if ((ns == st_namespace_any) || (ns == st_namespace_rhcs)) {
509 count += stonith__list_rhcs_agents(devices);
510 }
511
512 return count;
513 }
514
515 // See stonith_api_operations_t:metadata() documentation
516 static int
517 stonith_api_device_metadata(stonith_t *stonith, int call_options,
518 const char *agent, const char *namespace_s,
519 char **output, int timeout_sec)
520 {
521 /* By executing meta-data directly, we can get it from stonith_admin when
522 * the cluster is not running, which is important for higher-level tools.
523 */
524
525 enum stonith_namespace ns = get_namespace_from_agent(agent);
526
527 if (timeout_sec <= 0) {
528 timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
529 }
530
531 pcmk__trace("Looking up metadata for %s agent %s", namespace_text(ns),
532 agent);
533
534 switch (ns) {
535 case st_namespace_rhcs:
536 return stonith__rhcs_metadata(agent, timeout_sec, output);
537
538 #if HAVE_STONITH_STONITH_H
539 case st_namespace_lha:
540 return stonith__lha_metadata(agent, timeout_sec, output);
541 #endif
542
543 default:
544 pcmk__err("Can't get fence agent '%s' meta-data: No such agent",
545 agent);
546 break;
547 }
548 return -ENODEV;
549 }
550
551 static int
552 stonith_api_query(stonith_t * stonith, int call_options, const char *target,
553 stonith_key_value_t ** devices, int timeout)
554 {
555 int rc = 0, lpc = 0, max = 0;
556
557 xmlNode *data = NULL;
558 xmlNode *output = NULL;
559 xmlXPathObject *xpathObj = NULL;
560
561 CRM_CHECK(devices != NULL, return -EINVAL);
562
563 data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
564 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
565 pcmk__xe_set(data, PCMK__XA_ST_TARGET, target);
566 pcmk__xe_set(data, PCMK__XA_ST_DEVICE_ACTION, PCMK_ACTION_OFF);
567 rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout);
568
569 if (rc < 0) {
570 return rc;
571 }
572
573 xpathObj = pcmk__xpath_search(output->doc, "//*[@" PCMK_XA_AGENT "]");
574 if (xpathObj) {
575 max = pcmk__xpath_num_results(xpathObj);
576
577 for (lpc = 0; lpc < max; lpc++) {
578 xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
579
580 CRM_LOG_ASSERT(match != NULL);
581 if(match != NULL) {
582 const char *match_id = pcmk__xe_get(match, PCMK_XA_ID);
583 xmlChar *match_path = xmlGetNodePath(match);
584
585 pcmk__info("//*[@" PCMK_XA_AGENT "][%d] = %s", lpc, match_path);
586 free(match_path);
587 *devices = stonith__key_value_add(*devices, NULL, match_id);
588 }
589 }
590
591 xmlXPathFreeObject(xpathObj);
592 }
593
594 pcmk__xml_free(output);
595 pcmk__xml_free(data);
596 return max;
597 }
598
599 /*!
600 * \internal
601 * \brief Make a STONITH_OP_EXEC request
602 *
603 * \param[in,out] stonith Fencer connection
604 * \param[in] call_options Bitmask of \c stonith_call_options
605 * \param[in] id Fence device ID that request is for
606 * \param[in] action Agent action to request (list, status, monitor)
607 * \param[in] target Name of target node for requested action
608 * \param[in] timeout_sec Error if not completed within this many seconds
609 * \param[out] output Where to set agent output
610 */
611 static int
612 stonith_api_call(stonith_t *stonith, int call_options, const char *id,
613 const char *action, const char *target, int timeout_sec,
614 xmlNode **output)
615 {
616 int rc = 0;
617 xmlNode *data = NULL;
618
619 data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
620 pcmk__xe_set(data, PCMK__XA_ST_ORIGIN, __func__);
621 pcmk__xe_set(data, PCMK__XA_ST_DEVICE_ID, id);
622 pcmk__xe_set(data, PCMK__XA_ST_DEVICE_ACTION, action);
623 pcmk__xe_set(data, PCMK__XA_ST_TARGET, target);
624
625 rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output,
626 call_options, timeout_sec);
627 pcmk__xml_free(data);
628
629 return rc;
630 }
631
632 static int
633 stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info,
634 int timeout)
635 {
636 int rc;
637 xmlNode *output = NULL;
638
639 rc = stonith_api_call(stonith, call_options, id, PCMK_ACTION_LIST, NULL,
640 timeout, &output);
641
642 if (output && list_info) {
643 const char *list_str;
644
645 list_str = pcmk__xe_get(output, PCMK__XA_ST_OUTPUT);
646
647 if (list_str) {
648 *list_info = strdup(list_str);
649 }
650 }
651
652 pcmk__xml_free(output);
653 return rc;
654 }
655
656 static int
657 stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout)
658 {
659 return stonith_api_call(stonith, call_options, id, PCMK_ACTION_MONITOR,
660 NULL, timeout, NULL);
661 }
662
663 static int
664 stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port,
665 int timeout)
666 {
667 return stonith_api_call(stonith, call_options, id, PCMK_ACTION_STATUS, port,
668 timeout, NULL);
669 }
670
671 static int
672 stonith_api_fence_with_delay(stonith_t * stonith, int call_options, const char *node,
673 const char *action, int timeout, int tolerance, int delay)
674 {
675 int rc = 0;
676 xmlNode *data = NULL;
677
678 data = pcmk__xe_create(NULL, __func__);
679 pcmk__xe_set(data, PCMK__XA_ST_TARGET, node);
680 pcmk__xe_set(data, PCMK__XA_ST_DEVICE_ACTION, action);
681 pcmk__xe_set_int(data, PCMK__XA_ST_TIMEOUT, timeout);
682 pcmk__xe_set_int(data, PCMK__XA_ST_TOLERANCE, tolerance);
683 pcmk__xe_set_int(data, PCMK__XA_ST_DELAY, delay);
684
685 rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout);
686 pcmk__xml_free(data);
687
688 return rc;
689 }
690
691 static int
692 stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action,
693 int timeout, int tolerance)
694 {
695 return stonith_api_fence_with_delay(stonith, call_options, node, action,
696 timeout, tolerance, 0);
697 }
698
699 static int
700 stonith_api_confirm(stonith_t * stonith, int call_options, const char *target)
701 {
702 stonith__set_call_options(call_options, target, st_opt_manual_ack);
703 return stonith_api_fence(stonith, call_options, target, PCMK_ACTION_OFF, 0,
704 0);
705 }
706
707 static int
708 stonith_api_history(stonith_t * stonith, int call_options, const char *node,
709 stonith_history_t ** history, int timeout)
710 {
711 int rc = 0;
712 xmlNode *data = NULL;
713 xmlNode *output = NULL;
714 stonith_history_t *last = NULL;
715
716 *history = NULL;
717
718 if (node) {
719 data = pcmk__xe_create(NULL, __func__);
720 pcmk__xe_set(data, PCMK__XA_ST_TARGET, node);
721 }
722
723 stonith__set_call_options(call_options, node, st_opt_sync_call);
724 rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output,
725 call_options, timeout);
726 pcmk__xml_free(data);
727
728 if (rc == 0) {
729 xmlNode *op = NULL;
730 xmlNode *reply = pcmk__xpath_find_one(output->doc,
731 "//" PCMK__XE_ST_HISTORY,
732 PCMK__LOG_NEVER);
733
734 for (op = pcmk__xe_first_child(reply, NULL, NULL, NULL); op != NULL;
735 op = pcmk__xe_next(op, NULL)) {
736 stonith_history_t *kvp =
737 pcmk__assert_alloc(1, sizeof(stonith_history_t));
738 long long completed_nsec = 0LL;
739
740 kvp->target = pcmk__xe_get_copy(op, PCMK__XA_ST_TARGET);
741 kvp->action = pcmk__xe_get_copy(op, PCMK__XA_ST_DEVICE_ACTION);
742 kvp->origin = pcmk__xe_get_copy(op, PCMK__XA_ST_ORIGIN);
743 kvp->delegate = pcmk__xe_get_copy(op, PCMK__XA_ST_DELEGATE);
744 kvp->client = pcmk__xe_get_copy(op, PCMK__XA_ST_CLIENTNAME);
745 pcmk__xe_get_time(op, PCMK__XA_ST_DATE, &kvp->completed);
746
747 pcmk__xe_get_ll(op, PCMK__XA_ST_DATE_NSEC, &completed_nsec);
748
749 // Coverity complains here if long is the same size as long long
750 // coverity[result_independent_of_operands:FALSE]
751 if ((completed_nsec >= LONG_MIN) && (completed_nsec <= LONG_MAX)) {
752 kvp->completed_nsec = (long) completed_nsec;
753 }
754
755 pcmk__xe_get_int(op, PCMK__XA_ST_STATE, &kvp->state);
756 kvp->exit_reason = pcmk__xe_get_copy(op, PCMK_XA_EXIT_REASON);
757
758 if (last) {
759 last->next = kvp;
760 } else {
761 *history = kvp;
762 }
763 last = kvp;
764 }
765 }
766
767 pcmk__xml_free(output);
768
769 return rc;
770 }
771
772 /*!
773 * \internal
774 * \brief Free a list of fencing history objects and all members of each object
775 *
776 * \param[in,out] head Head of fencing history object list
777 */
778 void
779 stonith__history_free(stonith_history_t *head)
780 {
781 /* @COMPAT Drop "next" member of stonith_history_t, use a GList or GSList,
782 * and use the appropriate free function (while ensuring the members get
783 * freed)
784 */
785 while (head != NULL) {
786 stonith_history_t *next = head->next;
787
788 free(head->target);
789 free(head->action);
790 free(head->origin);
791 free(head->delegate);
792 free(head->client);
793 free(head->exit_reason);
794 free(head);
795 head = next;
796 }
797 }
798
799 static gint
800 stonithlib_GCompareFunc(gconstpointer a, gconstpointer b)
801 {
802 int rc = 0;
803 const stonith_notify_client_t *a_client = a;
804 const stonith_notify_client_t *b_client = b;
805
806 if (a_client->delete || b_client->delete) {
807 /* make entries marked for deletion not findable */
808 return -1;
809 }
810 CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
811 rc = strcmp(a_client->event, b_client->event);
812 if (rc == 0) {
813 if (a_client->notify == NULL || b_client->notify == NULL) {
814 return 0;
815
816 } else if (a_client->notify == b_client->notify) {
817 return 0;
818
819 } else if (((long)a_client->notify) < ((long)b_client->notify)) {
820 pcmk__err("callbacks for %s are not equal: %p vs. %p",
821 a_client->event, a_client->notify, b_client->notify);
822 return -1;
823 }
824 pcmk__err("callbacks for %s are not equal: %p vs. %p",
825 a_client->event, a_client->notify, b_client->notify);
826 return 1;
827 }
828 return rc;
829 }
830
831 xmlNode *
832 stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options)
833 {
834 xmlNode *op_msg = NULL;
835
836 CRM_CHECK(token != NULL, return NULL);
837
838 op_msg = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
839 pcmk__xe_set(op_msg, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
840 pcmk__xe_set(op_msg, PCMK__XA_ST_OP, op);
841 pcmk__xe_set_int(op_msg, PCMK__XA_ST_CALLID, call_id);
842 pcmk__trace("Sending call options: %.8lx, %d", (long) call_options,
843 call_options);
844 pcmk__xe_set_int(op_msg, PCMK__XA_ST_CALLOPT, call_options);
845
846 if (data != NULL) {
847 xmlNode *wrapper = pcmk__xe_create(op_msg, PCMK__XE_ST_CALLDATA);
848
849 pcmk__xml_copy(wrapper, data);
850 }
851
852 return op_msg;
853 }
854
855 static void
856 stonith_destroy_op_callback(gpointer data)
857 {
858 stonith_callback_client_t *blob = data;
859
860 if (blob->timer && blob->timer->ref > 0) {
861 g_source_remove(blob->timer->ref);
862 }
863 free(blob->timer);
864 free(blob);
865 }
866
867 static int
868 stonith_api_signoff(stonith_t * stonith)
869 {
870 stonith_private_t *native = stonith->st_private;
871
|
(1) Event path: |
Switch case default. |
|
(2) Event path: |
Condition "trace_cs == NULL", taking true branch. |
|
(3) Event path: |
Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch. |
|
(4) Event path: |
Breaking from switch. |
872 pcmk__debug("Disconnecting from the fencer");
873
|
(5) Event path: |
Condition "native->source != NULL", taking true branch. |
874 if (native->source != NULL) {
875 /* Attached to mainloop */
|
CID (unavailable; MK=d523a9f93fb49e2ebf2c7431218fcc00) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(6) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(7) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
876 g_clear_pointer(&native->source, mainloop_del_ipc_client);
877 native->ipc = NULL;
878
879 } else if (native->ipc) {
880 /* Not attached to mainloop */
881 crm_ipc_t *ipc = native->ipc;
882
883 native->ipc = NULL;
884 crm_ipc_close(ipc);
885 crm_ipc_destroy(ipc);
886 }
887
888 g_clear_pointer(&native->token, free);
889 stonith->state = stonith_disconnected;
890 return pcmk_ok;
891 }
892
893 static int
894 stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks)
895 {
896 stonith_private_t *private = stonith->st_private;
897
898 if (all_callbacks) {
899 private->op_callback = NULL;
900 g_hash_table_destroy(private->stonith_op_callback_table);
901 private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
902
903 } else if (call_id == 0) {
904 private->op_callback = NULL;
905
906 } else {
907 pcmk__intkey_table_remove(private->stonith_op_callback_table, call_id);
908 }
909 return pcmk_ok;
910 }
911
912 /*!
913 * \internal
914 * \brief Invoke a (single) specified fence action callback
915 *
916 * \param[in,out] st Fencer API connection
917 * \param[in] call_id If positive, call ID of completed fence action,
918 * otherwise legacy return code for early failure
919 * \param[in,out] result Full result for action
920 * \param[in,out] userdata User data to pass to callback
921 * \param[in] callback Fence action callback to invoke
922 */
923 static void
924 invoke_fence_action_callback(stonith_t *st, int call_id,
925 pcmk__action_result_t *result,
926 void *userdata,
927 void (*callback) (stonith_t *st,
928 stonith_callback_data_t *data))
929 {
930 stonith_callback_data_t data = { 0, };
931
932 data.call_id = call_id;
933 data.rc = pcmk_rc2legacy(stonith__result2rc(result));
934 data.userdata = userdata;
935 data.opaque = (void *) result;
936
937 callback(st, &data);
938 }
939
940 /*!
941 * \internal
942 * \brief Invoke any callbacks registered for a specified fence action result
943 *
944 * Given a fence action result from the fencer, invoke any callback registered
945 * for that action, as well as any global callback registered.
946 *
947 * \param[in,out] stonith Fencer API connection
948 * \param[in] msg If non-NULL, fencer reply
949 * \param[in] call_id If \p msg is NULL, call ID of action that timed out
950 */
951 static void
952 invoke_registered_callbacks(stonith_t *stonith, const xmlNode *msg, int call_id)
953 {
954 stonith_private_t *private = NULL;
955 stonith_callback_client_t *cb_info = NULL;
956 pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
957
958 CRM_CHECK(stonith != NULL, return);
959 CRM_CHECK(stonith->st_private != NULL, return);
960
961 private = stonith->st_private;
962
963 if (msg == NULL) {
964 // Fencer didn't reply in time
965 pcmk__set_result(&result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT,
966 "Fencer accepted request but did not reply in time");
967 CRM_LOG_ASSERT(call_id > 0);
968
969 } else {
970 // We have the fencer reply
971 if ((pcmk__xe_get_int(msg, PCMK__XA_ST_CALLID, &call_id) != pcmk_rc_ok)
972 || (call_id <= 0)) {
973 pcmk__log_xml_warn(msg, "Bad fencer reply");
974 }
975 stonith__xe_get_result(msg, &result);
976 }
977
978 if (call_id > 0) {
979 cb_info = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
980 call_id);
981 }
982
983 if ((cb_info != NULL) && (cb_info->callback != NULL)
984 && (pcmk__result_ok(&result) || !(cb_info->only_success))) {
985 pcmk__trace("Invoking callback %s for call %d",
986 pcmk__s(cb_info->id, "without ID"), call_id);
987 invoke_fence_action_callback(stonith, call_id, &result,
988 cb_info->user_data, cb_info->callback);
989
990 } else if ((private->op_callback == NULL) && !pcmk__result_ok(&result)) {
991 pcmk__warn("Fencing action without registered callback failed: %d "
992 "(%s%s%s)",
993 result.exit_status,
994 pcmk_exec_status_str(result.execution_status),
995 ((result.exit_reason != NULL)? ": " : ""),
996 pcmk__s(result.exit_reason, ""));
997 pcmk__log_xml_debug(msg, "Failed fence update");
998 }
999
1000 if (private->op_callback != NULL) {
1001 pcmk__trace("Invoking global callback for call %d", call_id);
1002 invoke_fence_action_callback(stonith, call_id, &result, NULL,
1003 private->op_callback);
1004 }
1005
1006 if (cb_info != NULL) {
1007 stonith_api_del_callback(stonith, call_id, FALSE);
1008 }
1009 pcmk__reset_result(&result);
1010 }
1011
1012 static gboolean
1013 stonith_async_timeout_handler(gpointer data)
1014 {
1015 struct timer_rec_s *timer = data;
1016
1017 pcmk__err("Async call %d timed out after %dms", timer->call_id,
1018 timer->timeout);
1019 invoke_registered_callbacks(timer->stonith, NULL, timer->call_id);
1020
1021 /* Always return TRUE, never remove the handler
1022 * We do that in stonith_del_callback()
1023 */
1024 return TRUE;
1025 }
1026
1027 static void
1028 set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id,
1029 int timeout)
1030 {
1031 struct timer_rec_s *async_timer = callback->timer;
1032
1033 if (timeout <= 0) {
1034 return;
1035 }
1036
1037 if (!async_timer) {
1038 async_timer = pcmk__assert_alloc(1, sizeof(struct timer_rec_s));
1039 callback->timer = async_timer;
1040 }
1041
1042 async_timer->stonith = stonith;
1043 async_timer->call_id = call_id;
1044 /* Allow a fair bit of grace to allow the server to tell us of a timeout
1045 * This is only a fallback
1046 */
1047 async_timer->timeout = (timeout + 60) * 1000;
1048 if (async_timer->ref) {
1049 g_source_remove(async_timer->ref);
1050 }
1051 async_timer->ref =
1052 pcmk__create_timer(async_timer->timeout, stonith_async_timeout_handler,
1053 async_timer);
1054 }
1055
1056 static void
1057 update_callback_timeout(int call_id, int timeout, stonith_t * st)
1058 {
1059 stonith_callback_client_t *callback = NULL;
1060 stonith_private_t *private = st->st_private;
1061
1062 callback = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
1063 call_id);
1064 if (!callback || !callback->allow_timeout_updates) {
1065 return;
1066 }
1067
1068 set_callback_timeout(callback, st, call_id, timeout);
1069 }
1070
1071 static int
1072 stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata)
1073 {
1074 const char *type = NULL;
1075 struct notify_blob_s blob;
1076
1077 stonith_t *st = userdata;
1078 stonith_private_t *private = NULL;
1079
1080 pcmk__assert(st != NULL);
1081 private = st->st_private;
1082
1083 blob.stonith = st;
1084 blob.xml = pcmk__xml_parse(buffer);
1085 if (blob.xml == NULL) {
1086 pcmk__warn("Received malformed message from fencer: %s", buffer);
1087 return 0;
1088 }
1089
1090 /* do callbacks */
1091 type = pcmk__xe_get(blob.xml, PCMK__XA_T);
1092 pcmk__trace("Activating %s callbacks...", type);
1093
1094 if (pcmk__str_eq(type, PCMK__VALUE_STONITH_NG, pcmk__str_none)) {
1095 invoke_registered_callbacks(st, blob.xml, 0);
1096
1097 } else if (pcmk__str_eq(type, PCMK__VALUE_ST_NOTIFY, pcmk__str_none)) {
1098 foreach_notify_entry(private, stonith_send_notification, &blob);
1099
1100 } else if (pcmk__str_eq(type, PCMK__VALUE_ST_ASYNC_TIMEOUT_VALUE,
1101 pcmk__str_none)) {
1102 int call_id = 0;
1103 int timeout = 0;
1104
1105 pcmk__xe_get_int(blob.xml, PCMK__XA_ST_TIMEOUT, &timeout);
1106 pcmk__xe_get_int(blob.xml, PCMK__XA_ST_CALLID, &call_id);
1107
1108 update_callback_timeout(call_id, timeout, st);
1109 } else {
1110 pcmk__err("Unknown message type: %s", type);
1111 pcmk__log_xml_warn(blob.xml, "BadReply");
1112 }
1113
1114 pcmk__xml_free(blob.xml);
1115 return 1;
1116 }
1117
1118 static bool
1119 ack_is_failure(const xmlNode *reply)
1120 {
1121 int status = 0;
1122
1123 pcmk__xe_get_int(reply, PCMK_XA_STATUS, &status);
1124 if (status != CRM_EX_OK) {
1125 pcmk__err("Received error response from fenced: %s", crm_exit_str(status));
1126 return true;
1127 }
1128
1129 return false;
1130 }
1131
1132 static int
1133 stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd)
1134 {
1135 int rc = pcmk_ok;
1136 stonith_private_t *native = NULL;
1137 const char *display_name = name? name : "client";
1138
1139 xmlNode *hello = NULL;
1140 xmlNode *reply = NULL;
1141 const char *msg_type = NULL;
1142
1143 struct ipc_client_callbacks st_callbacks = {
1144 .dispatch = stonith_dispatch_internal,
1145 .destroy = stonith_connection_destroy
1146 };
1147
1148 CRM_CHECK(stonith != NULL, return -EINVAL);
1149
1150 native = stonith->st_private;
1151 pcmk__assert(native != NULL);
1152
1153 pcmk__debug("Attempting fencer connection by %s with%s mainloop",
1154 display_name, ((stonith_fd != 0)? "out" : ""));
1155
1156 stonith->state = stonith_connected_command;
1157 if (stonith_fd) {
1158 /* No mainloop */
1159 native->ipc = crm_ipc_new("stonith-ng", 0);
1160 if (native->ipc != NULL) {
1161 rc = pcmk__connect_generic_ipc(native->ipc);
1162 if (rc == pcmk_rc_ok) {
1163 rc = pcmk__ipc_fd(native->ipc, stonith_fd);
1164 if (rc != pcmk_rc_ok) {
1165 pcmk__debug("Couldn't get file descriptor for IPC: %s",
1166 pcmk_rc_str(rc));
1167 }
1168 }
1169 if (rc != pcmk_rc_ok) {
1170 crm_ipc_close(native->ipc);
1171 g_clear_pointer(&native->ipc, crm_ipc_destroy);
1172 }
1173 }
1174
1175 } else {
1176 /* With mainloop */
1177 native->source =
1178 mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks);
1179 native->ipc = mainloop_get_ipc_client(native->source);
1180 }
1181
1182 if (native->ipc == NULL) {
1183 rc = -ENOTCONN;
1184 goto done;
1185 }
1186
1187 hello = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
1188
1189 pcmk__xe_set(hello, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
1190 pcmk__xe_set(hello, PCMK__XA_ST_OP, CRM_OP_REGISTER);
1191 pcmk__xe_set(hello, PCMK__XA_ST_CLIENTNAME, name);
1192 rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply);
1193
1194 if (rc < 0) {
1195 pcmk__debug("Couldn't register with the fencer: %s " QB_XS " rc=%d",
1196 pcmk_strerror(rc), rc);
1197 rc = -ECOMM;
1198 goto done;
1199 }
1200
1201 if (reply == NULL) {
1202 pcmk__debug("Couldn't register with the fencer: no reply");
1203 rc = -EPROTO;
1204 goto done;
1205 }
1206
1207 /* The only reason we can receive an ACK here is if fenced_ipc_dispatch ->
1208 * pcmk__client_data2xml processed something that's not valid XML.
1209 * fenced_ipc_disaptch does not return ACK from handle_unknown_request,
1210 * unlike other daemons.
1211 */
1212 if (pcmk__xe_is(reply, PCMK__XE_ACK) && ack_is_failure(reply)) {
1213 rc = -EPROTO;
1214 goto done;
1215 }
1216
1217 msg_type = pcmk__xe_get(reply, PCMK__XA_ST_OP);
1218
1219 native->token = pcmk__xe_get_copy(reply, PCMK__XA_ST_CLIENTID);
1220 if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_none)) {
1221 pcmk__debug("Couldn't register with the fencer: invalid reply "
1222 "type '%s'",
1223 pcmk__s(msg_type, "(missing)"));
1224 pcmk__log_xml_debug(reply, "Invalid fencer reply");
1225 rc = -EPROTO;
1226
1227 } else if (native->token == NULL) {
1228 pcmk__debug("Couldn't register with the fencer: no token in "
1229 "reply");
1230 pcmk__log_xml_debug(reply, "Invalid fencer reply");
1231 rc = -EPROTO;
1232
1233 } else {
1234 pcmk__debug("Connection to fencer by %s succeeded "
1235 "(registration token: %s)",
1236 display_name, native->token);
1237 rc = pcmk_ok;
1238 }
1239
1240 done:
1241 pcmk__xml_free(reply);
1242 pcmk__xml_free(hello);
1243
1244 if (rc != pcmk_ok) {
1245 pcmk__debug("Connection attempt to fencer by %s failed: %s "
1246 QB_XS " rc=%d",
1247 display_name, pcmk_strerror(rc), rc);
1248 stonith->cmds->disconnect(stonith);
1249 }
1250 return rc;
1251 }
1252
1253 static int
1254 stonith_set_notification(stonith_t * stonith, const char *callback, int enabled)
1255 {
1256 int rc = pcmk_ok;
1257 xmlNode *notify_msg = pcmk__xe_create(NULL, __func__);
1258 stonith_private_t *native = stonith->st_private;
1259
1260 if (stonith->state != stonith_disconnected) {
1261
1262 pcmk__xe_set(notify_msg, PCMK__XA_ST_OP, STONITH_OP_NOTIFY);
1263 if (enabled) {
1264 pcmk__xe_set(notify_msg, PCMK__XA_ST_NOTIFY_ACTIVATE, callback);
1265 } else {
1266 pcmk__xe_set(notify_msg, PCMK__XA_ST_NOTIFY_DEACTIVATE, callback);
1267 }
1268
1269 /* We don't care about the reply here, so there's no need to check
1270 * if we got an ACK in response.
1271 */
1272 rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL);
1273 if (rc < 0) {
1274 pcmk__debug("Couldn't register for fencing notifications: %s",
1275 pcmk_strerror(rc));
1276 rc = -ECOMM;
1277 } else {
1278 rc = pcmk_ok;
1279 }
1280 }
1281
1282 pcmk__xml_free(notify_msg);
1283 return rc;
1284 }
1285
1286 static int
1287 stonith_api_add_notification(stonith_t * stonith, const char *event,
1288 void (*callback) (stonith_t * stonith, stonith_event_t * e))
1289 {
1290 GList *list_item = NULL;
1291 stonith_notify_client_t *new_client = NULL;
1292 stonith_private_t *private = NULL;
1293
1294 private = stonith->st_private;
1295 pcmk__trace("Adding callback for %s events (%u)", event,
1296 g_list_length(private->notify_list));
1297
1298 new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
1299 new_client->event = event;
1300 new_client->notify = callback;
1301
1302 list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
1303
1304 if (list_item != NULL) {
1305 pcmk__warn("Callback already present");
1306 free(new_client);
1307 return -ENOTUNIQ;
1308
1309 } else {
1310 private->notify_list = g_list_append(private->notify_list, new_client);
1311
1312 stonith_set_notification(stonith, event, 1);
1313
1314 pcmk__trace("Callback added (%u)", g_list_length(private->notify_list));
1315 }
1316 return pcmk_ok;
1317 }
1318
1319 static void
1320 del_notify_entry(gpointer data, gpointer user_data)
1321 {
1322 stonith_notify_client_t *entry = data;
1323 stonith_t * stonith = user_data;
1324
1325 if (!entry->delete) {
1326 pcmk__debug("Removing callback for %s events", entry->event);
1327 stonith_api_del_notification(stonith, entry->event);
1328 }
1329 }
1330
1331 static int
1332 stonith_api_del_notification(stonith_t * stonith, const char *event)
1333 {
1334 GList *list_item = NULL;
1335 stonith_notify_client_t *new_client = NULL;
1336 stonith_private_t *private = stonith->st_private;
1337
1338 if (event == NULL) {
1339 foreach_notify_entry(private, del_notify_entry, stonith);
1340 pcmk__trace("Removed callback");
1341
1342 return pcmk_ok;
1343 }
1344
1345 pcmk__debug("Removing callback for %s events", event);
1346
1347 new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
1348 new_client->event = event;
1349 new_client->notify = NULL;
1350
1351 list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
1352
1353 stonith_set_notification(stonith, event, 0);
1354
1355 if (list_item != NULL) {
1356 stonith_notify_client_t *list_client = list_item->data;
1357
1358 if (private->notify_refcnt) {
1359 list_client->delete = TRUE;
1360 private->notify_deletes = TRUE;
1361 } else {
1362 private->notify_list = g_list_remove(private->notify_list, list_client);
1363 free(list_client);
1364 }
1365
1366 pcmk__trace("Removed callback");
1367
1368 } else {
1369 pcmk__trace("Callback not present");
1370 }
1371 free(new_client);
1372 return pcmk_ok;
1373 }
1374
1375 static int
1376 stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options,
1377 void *user_data, const char *callback_name,
1378 void (*callback) (stonith_t * st, stonith_callback_data_t * data))
1379 {
1380 stonith_callback_client_t *blob = NULL;
1381 stonith_private_t *private = NULL;
1382
1383 CRM_CHECK(stonith != NULL, return -EINVAL);
1384 CRM_CHECK(stonith->st_private != NULL, return -EINVAL);
1385 private = stonith->st_private;
1386
1387 if (call_id == 0) { // Add global callback
1388 private->op_callback = callback;
1389
1390 } else if (call_id < 0) { // Call failed immediately, so call callback now
1391 if (!(options & st_opt_report_only_success)) {
1392 pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
1393
1394 pcmk__trace("Call failed, calling %s: %s", callback_name,
1395 pcmk_strerror(call_id));
1396 pcmk__set_result(&result, CRM_EX_ERROR,
1397 stonith__legacy2status(call_id), NULL);
1398 invoke_fence_action_callback(stonith, call_id, &result,
1399 user_data, callback);
1400 } else {
1401 pcmk__warn("Fencer call failed: %s", pcmk_strerror(call_id));
1402 }
1403 return FALSE;
1404 }
1405
1406 blob = pcmk__assert_alloc(1, sizeof(stonith_callback_client_t));
1407 blob->id = callback_name;
1408 blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE;
1409 blob->user_data = user_data;
1410 blob->callback = callback;
1411 blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE;
1412
1413 if (timeout > 0) {
1414 set_callback_timeout(blob, stonith, call_id, timeout);
1415 }
1416
1417 pcmk__intkey_table_insert(private->stonith_op_callback_table, call_id,
1418 blob);
1419 pcmk__trace("Added callback to %s for call %d", callback_name, call_id);
1420
1421 return TRUE;
1422 }
1423
1424 /*!
1425 * \internal
1426 * \brief Get the data section of a fencer notification
1427 *
1428 * \param[in] msg Notification XML
1429 * \param[in] ntype Notification type
1430 */
1431 static xmlNode *
1432 get_event_data_xml(xmlNode *msg, const char *ntype)
1433 {
1434 char *data_addr = pcmk__assert_asprintf("//%s", ntype);
1435 xmlNode *data = pcmk__xpath_find_one(msg->doc, data_addr, LOG_DEBUG);
1436
1437 free(data_addr);
1438 return data;
1439 }
1440
1441 /*
1442 <notify t="st_notify" subt="st_device_register" st_op="st_device_register" st_rc="0" >
1443 <st_calldata >
1444 <stonith_command t="stonith-ng" st_async_id="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_op="st_device_register" st_callid="2" st_callopt="4096" st_timeout="0" st_clientid="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_clientname="cts-fence-helper" >
1445 <st_calldata >
1446 <st_device_id id="test-id" origin="create_device_registration_xml" agent="fence_virsh" namespace="stonith-ng" >
1447 <attributes ipaddr="localhost" pcmk-portmal="some-host=pcmk-1 pcmk-3=3,4" login="root" identity_file="/root/.ssh/id_dsa" />
1448 </st_device_id>
1449 </st_calldata>
1450 </stonith_command>
1451 </st_calldata>
1452 </notify>
1453
1454 <notify t="st_notify" subt="st_notify_fence" st_op="st_notify_fence" st_rc="0" >
1455 <st_calldata >
1456 <st_notify_fence st_rc="0" st_target="some-host" st_op="st_fence" st_delegate="test-id" st_origin="61dd7759-e229-4be7-b1f8-ef49dd14d9f0" />
1457 </st_calldata>
1458 </notify>
1459 */
1460 static stonith_event_t *
1461 xml_to_event(xmlNode *msg)
1462 {
1463 stonith_event_t *event = pcmk__assert_alloc(1, sizeof(stonith_event_t));
1464 struct event_private *event_private = NULL;
1465
1466 event->opaque = pcmk__assert_alloc(1, sizeof(struct event_private));
1467 event_private = (struct event_private *) event->opaque;
1468
1469 pcmk__log_xml_trace(msg, "stonith_notify");
1470
1471 // All notification types have the operation result and notification subtype
1472 stonith__xe_get_result(msg, &event_private->result);
1473 event->operation = pcmk__xe_get_copy(msg, PCMK__XA_ST_OP);
1474
1475 // @COMPAT The API originally provided the result as a legacy return code
1476 event->result = pcmk_rc2legacy(stonith__result2rc(&event_private->result));
1477
1478 // Some notification subtypes have additional information
1479
1480 if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_FENCE,
1481 pcmk__str_none)) {
1482 xmlNode *data = get_event_data_xml(msg, event->operation);
1483
1484 if (data == NULL) {
1485 pcmk__err("No data for %s event", event->operation);
1486 pcmk__log_xml_notice(msg, "BadEvent");
1487 } else {
1488 event->origin = pcmk__xe_get_copy(data, PCMK__XA_ST_ORIGIN);
1489 event->action = pcmk__xe_get_copy(data, PCMK__XA_ST_DEVICE_ACTION);
1490 event->target = pcmk__xe_get_copy(data, PCMK__XA_ST_TARGET);
1491 event->executioner = pcmk__xe_get_copy(data, PCMK__XA_ST_DELEGATE);
1492 event->id = pcmk__xe_get_copy(data, PCMK__XA_ST_REMOTE_OP);
1493 event->client_origin = pcmk__xe_get_copy(data,
1494 PCMK__XA_ST_CLIENTNAME);
1495 event->device = pcmk__xe_get_copy(data, PCMK__XA_ST_DEVICE_ID);
1496 }
1497
1498 } else if (pcmk__str_any_of(event->operation,
1499 STONITH_OP_DEVICE_ADD, STONITH_OP_DEVICE_DEL,
1500 STONITH_OP_LEVEL_ADD, STONITH_OP_LEVEL_DEL,
1501 NULL)) {
1502 xmlNode *data = get_event_data_xml(msg, event->operation);
1503
1504 if (data == NULL) {
1505 pcmk__err("No data for %s event", event->operation);
1506 pcmk__log_xml_notice(msg, "BadEvent");
1507 } else {
1508 event->device = pcmk__xe_get_copy(data, PCMK__XA_ST_DEVICE_ID);
1509 }
1510 }
1511
1512 return event;
1513 }
1514
1515 static void
1516 event_free(stonith_event_t * event)
1517 {
1518 struct event_private *event_private = event->opaque;
1519
1520 free(event->id);
1521 free(event->operation);
1522 free(event->origin);
1523 free(event->action);
1524 free(event->target);
1525 free(event->executioner);
1526 free(event->device);
1527 free(event->client_origin);
1528 pcmk__reset_result(&event_private->result);
1529 free(event->opaque);
1530 free(event);
1531 }
1532
1533 static void
1534 stonith_send_notification(gpointer data, gpointer user_data)
1535 {
1536 struct notify_blob_s *blob = user_data;
1537 stonith_notify_client_t *entry = data;
1538 stonith_event_t *st_event = NULL;
1539 const char *event = NULL;
1540
1541 if (blob->xml == NULL) {
1542 pcmk__warn("Skipping callback - NULL message");
1543 return;
1544 }
1545
1546 event = pcmk__xe_get(blob->xml, PCMK__XA_SUBT);
1547
1548 if (entry == NULL) {
1549 pcmk__warn("Skipping callback - NULL callback client");
1550 return;
1551
1552 } else if (entry->delete) {
1553 pcmk__trace("Skipping callback - marked for deletion");
1554 return;
1555
1556 } else if (entry->notify == NULL) {
1557 pcmk__warn("Skipping callback - NULL callback");
1558 return;
1559
1560 } else if (!pcmk__str_eq(entry->event, event, pcmk__str_none)) {
1561 pcmk__trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
1562 return;
1563 }
1564
1565 st_event = xml_to_event(blob->xml);
1566
1567 pcmk__trace("Invoking callback for %p/%s event...", entry, event);
1568 // coverity[null_field]
1569 entry->notify(blob->stonith, st_event);
1570 pcmk__trace("Callback invoked...");
1571
1572 event_free(st_event);
1573 }
1574
1575 /*!
1576 * \internal
1577 * \brief Create and send an API request
1578 *
1579 * \param[in,out] stonith Stonith connection
1580 * \param[in] op API operation to request
1581 * \param[in] data Data to attach to request
1582 * \param[out] output_data If not NULL, will be set to reply if synchronous
1583 * \param[in] call_options Bitmask of stonith_call_options to use
1584 * \param[in] timeout Error if not completed within this many seconds
1585 *
1586 * \return pcmk_ok (for synchronous requests) or positive call ID
1587 * (for asynchronous requests) on success, -errno otherwise
1588 */
1589 static int
1590 stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data,
1591 int call_options, int timeout)
1592 {
1593 int rc = 0;
1594 int reply_id = -1;
1595
1596 xmlNode *op_msg = NULL;
1597 xmlNode *op_reply = NULL;
1598 stonith_private_t *native = NULL;
1599
1600 pcmk__assert((stonith != NULL) && (stonith->st_private != NULL)
1601 && (op != NULL));
1602 native = stonith->st_private;
1603
1604 if (output_data != NULL) {
1605 *output_data = NULL;
1606 }
1607
1608 if ((stonith->state == stonith_disconnected) || (native->token == NULL)) {
1609 return -ENOTCONN;
1610 }
1611
1612 /* Increment the call ID, which must be positive to avoid conflicting with
1613 * error codes. This shouldn't be a problem unless the client mucked with
1614 * it or the counter wrapped around.
1615 */
1616 stonith->call_id++;
1617 if (stonith->call_id < 1) {
1618 stonith->call_id = 1;
1619 }
1620
1621 op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options);
1622 if (op_msg == NULL) {
1623 return -EINVAL;
1624 }
1625
1626 pcmk__xe_set_int(op_msg, PCMK__XA_ST_TIMEOUT, timeout);
1627 pcmk__trace("Sending %s message to fencer with timeout %ds", op, timeout);
1628
1629 if (data) {
1630 const char *delay_s = pcmk__xe_get(data, PCMK__XA_ST_DELAY);
1631
1632 if (delay_s) {
1633 pcmk__xe_set(op_msg, PCMK__XA_ST_DELAY, delay_s);
1634 }
1635 }
1636
1637 {
1638 enum crm_ipc_flags ipc_flags = crm_ipc_flags_none;
1639
1640 if (call_options & st_opt_sync_call) {
1641 pcmk__set_ipc_flags(ipc_flags, "fencing command",
1642 crm_ipc_client_response);
1643 }
1644 rc = crm_ipc_send(native->ipc, op_msg, ipc_flags,
1645 1000 * (timeout + 60), &op_reply);
1646 }
1647 pcmk__xml_free(op_msg);
1648
1649 if (rc < 0) {
1650 pcmk__err("Couldn't perform %s operation (timeout=%ds): %s", op,
1651 timeout, pcmk_strerror(rc));
1652 rc = -ECOMM;
1653 goto done;
1654 }
1655
1656 if (pcmk__xe_is(op_reply, PCMK__XE_ACK) && ack_is_failure(op_reply)) {
1657 rc = -EPROTO;
1658 goto done;
1659 }
1660
1661 pcmk__log_xml_trace(op_reply, "Reply");
1662
1663 if (!(call_options & st_opt_sync_call)) {
1664 pcmk__trace("Async call %d, returning", stonith->call_id);
1665 pcmk__xml_free(op_reply);
1666 return stonith->call_id;
1667 }
1668
1669 pcmk__xe_get_int(op_reply, PCMK__XA_ST_CALLID, &reply_id);
1670
1671 if (reply_id == stonith->call_id) {
1672 pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
1673
1674 pcmk__trace("Synchronous reply %d received", reply_id);
1675
1676 stonith__xe_get_result(op_reply, &result);
1677 rc = pcmk_rc2legacy(stonith__result2rc(&result));
1678 pcmk__reset_result(&result);
1679
1680 if ((call_options & st_opt_discard_reply) || output_data == NULL) {
1681 pcmk__trace("Discarding reply");
1682
1683 } else {
1684 *output_data = op_reply;
1685 op_reply = NULL; /* Prevent subsequent free */
1686 }
1687
1688 } else if (reply_id <= 0) {
1689 pcmk__err("Received bad reply: No id set");
1690 pcmk__log_xml_err(op_reply, "Bad reply");
1691
1692 g_clear_pointer(&op_reply, pcmk__xml_free);
1693 rc = -ENOMSG;
1694
1695 } else {
1696 pcmk__err("Received bad reply: %d (wanted %d)", reply_id,
1697 stonith->call_id);
1698 pcmk__log_xml_err(op_reply, "Old reply");
1699
1700 g_clear_pointer(&op_reply, pcmk__xml_free);
1701 rc = -ENOMSG;
1702 }
1703
1704 done:
1705 if (!crm_ipc_connected(native->ipc)) {
1706 pcmk__err("Fencer disconnected");
1707 g_clear_pointer(&native->token, free);
1708 stonith->state = stonith_disconnected;
1709 }
1710
1711 pcmk__xml_free(op_reply);
1712 return rc;
1713 }
1714
1715 /*!
1716 * \internal
1717 * \brief Process IPC messages for a fencer API connection
1718 *
1719 * This is used for testing purposes in scenarios that don't use a mainloop to
1720 * dispatch messages automatically.
1721 *
1722 * \param[in,out] stonith_api Fencer API connetion object
1723 *
1724 * \return Standard Pacemaker return code
1725 */
1726 int
1727 stonith__api_dispatch(stonith_t *stonith_api)
1728 {
1729 stonith_private_t *private = NULL;
1730
1731 pcmk__assert(stonith_api != NULL);
1732 private = stonith_api->st_private;
1733
1734 while (crm_ipc_ready(private->ipc)) {
1735 if (crm_ipc_read(private->ipc) > 0) {
1736 const char *msg = crm_ipc_buffer(private->ipc);
1737
1738 stonith_dispatch_internal(msg, strlen(msg), stonith_api);
1739 pcmk__ipc_free_client_buffer(private->ipc);
1740 }
1741
1742 if (!crm_ipc_connected(private->ipc)) {
1743 pcmk__err("Connection closed");
1744 return ENOTCONN;
1745 }
1746 }
1747
1748 return pcmk_rc_ok;
1749 }
1750
1751 static int
1752 free_stonith_api(stonith_t *stonith)
1753 {
1754 int rc = pcmk_ok;
1755
1756 pcmk__trace("Destroying %p", stonith);
1757
1758 if (stonith->state != stonith_disconnected) {
1759 pcmk__trace("Unregistering notifications and disconnecting %p first",
1760 stonith);
1761 stonith->cmds->remove_notification(stonith, NULL);
1762 rc = stonith->cmds->disconnect(stonith);
1763 }
1764
1765 if (stonith->state == stonith_disconnected) {
1766 stonith_private_t *private = stonith->st_private;
1767
1768 pcmk__trace("Removing %u callbacks",
1769 g_hash_table_size(private->stonith_op_callback_table));
1770 g_hash_table_destroy(private->stonith_op_callback_table);
1771
1772 pcmk__trace("Destroying %u notification clients",
1773 g_list_length(private->notify_list));
1774 g_list_free_full(private->notify_list, free);
1775
1776 free(stonith->st_private);
1777 free(stonith->cmds);
1778 free(stonith);
1779
1780 } else {
1781 pcmk__err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc),
1782 rc);
1783 }
1784
1785 return rc;
1786 }
1787
1788 static gboolean
1789 is_fencing_param(gpointer key, gpointer value, gpointer user_data)
1790 {
1791 return pcmk_stonith_param(key);
1792 }
1793
1794 int
1795 stonith__validate(stonith_t *st, int call_options, const char *rsc_id,
1796 const char *agent, GHashTable *params, int timeout_sec,
1797 char **output, char **error_output)
1798 {
1799 int rc = pcmk_rc_ok;
1800
1801 /* Use a dummy node name in case the agent requires a target. We assume the
1802 * actual target doesn't matter for validation purposes (if in practice,
1803 * that is incorrect, we will need to allow the caller to pass the target).
1804 */
1805 const char *target = "node1";
1806 char *host_arg = NULL;
1807
1808 if (params != NULL) {
1809 const char *param = g_hash_table_lookup(params,
1810 PCMK_FENCING_HOST_ARGUMENT);
1811
1812 host_arg = pcmk__str_copy(param);
1813
1814 /* Remove special fencing params from the table before doing anything
1815 * else
1816 */
1817 g_hash_table_foreach_remove(params, is_fencing_param, NULL);
1818 }
1819
1820 #if PCMK__ENABLE_CIBSECRETS
1821 rc = pcmk__substitute_secrets(rsc_id, params);
1822 if (rc != pcmk_rc_ok) {
1823 pcmk__warn("Could not replace secret parameters for validation of %s: "
1824 "%s",
1825 agent, pcmk_rc_str(rc));
1826 // rc is standard return value, don't return it in this function
1827 }
1828 #endif
1829
1830 if (output) {
1831 *output = NULL;
1832 }
1833 if (error_output) {
1834 *error_output = NULL;
1835 }
1836
1837 if (timeout_sec <= 0) {
1838 timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
1839 }
1840
1841 switch (get_namespace_from_agent(agent)) {
1842 case st_namespace_rhcs:
1843 rc = stonith__rhcs_validate(st, call_options, target, agent,
1844 params, host_arg, timeout_sec,
1845 output, error_output);
1846 rc = pcmk_legacy2rc(rc);
1847 break;
1848
1849 #if HAVE_STONITH_STONITH_H
1850 case st_namespace_lha:
1851 rc = stonith__lha_validate(st, call_options, target, agent,
1852 params, timeout_sec, output,
1853 error_output);
1854 rc = pcmk_legacy2rc(rc);
1855 break;
1856 #endif
1857
1858 case st_namespace_invalid:
1859 errno = ENOENT;
1860 rc = errno;
1861
1862 if (error_output) {
1863 *error_output = pcmk__assert_asprintf("Agent %s not found",
1864 agent);
1865 } else {
1866 pcmk__err("Agent %s not found", agent);
1867 }
1868
1869 break;
1870
1871 default:
1872 errno = EOPNOTSUPP;
1873 rc = errno;
1874
1875 if (error_output) {
1876 *error_output = pcmk__assert_asprintf("Agent %s does not "
1877 "support validation",
1878 agent);
1879 } else {
1880 pcmk__err("Agent %s does not support validation", agent);
1881 }
1882
1883 break;
1884 }
1885
1886 free(host_arg);
1887 return rc;
1888 }
1889
1890 static int
1891 stonith_api_validate(stonith_t *st, int call_options, const char *rsc_id,
1892 const char *namespace_s, const char *agent,
1893 const stonith_key_value_t *params, int timeout_sec,
1894 char **output, char **error_output)
1895 {
1896 /* Validation should be done directly via the agent, so we can get it from
1897 * stonith_admin when the cluster is not running, which is important for
1898 * higher-level tools.
1899 */
1900
1901 int rc = pcmk_ok;
1902
1903 GHashTable *params_table = pcmk__strkey_table(free, free);
1904
1905 // Convert parameter list to a hash table
1906 for (; params; params = params->next) {
1907 if (!pcmk_stonith_param(params->key)) {
1908 pcmk__insert_dup(params_table, params->key, params->value);
1909 }
1910 }
1911
1912 rc = stonith__validate(st, call_options, rsc_id, agent, params_table,
1913 timeout_sec, output, error_output);
1914
1915 g_hash_table_destroy(params_table);
1916 return rc;
1917 }
1918
1919 /*!
1920 * \internal
1921 * \brief Create a new fencer API connection object
1922 *
1923 * \return Newly allocated fencer API connection object, or \c NULL on
1924 * allocation failure
1925 */
1926 stonith_t *
1927 stonith__api_new(void)
1928 {
1929 stonith_t *new_stonith = NULL;
1930 stonith_private_t *private = NULL;
1931
1932 new_stonith = calloc(1, sizeof(stonith_t));
1933 if (new_stonith == NULL) {
1934 return NULL;
1935 }
1936
1937 private = calloc(1, sizeof(stonith_private_t));
1938 if (private == NULL) {
1939 free(new_stonith);
1940 return NULL;
1941 }
1942 new_stonith->st_private = private;
1943
1944 private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
1945 private->notify_list = NULL;
1946 private->notify_refcnt = 0;
1947 private->notify_deletes = FALSE;
1948
1949 new_stonith->call_id = 1;
1950 new_stonith->state = stonith_disconnected;
1951
1952 new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t));
1953 if (new_stonith->cmds == NULL) {
1954 free(new_stonith->st_private);
1955 free(new_stonith);
1956 return NULL;
1957 }
1958
1959 new_stonith->cmds->free = free_stonith_api;
1960 new_stonith->cmds->connect = stonith_api_signon;
1961 new_stonith->cmds->disconnect = stonith_api_signoff;
1962
1963 new_stonith->cmds->list = stonith_api_list;
1964 new_stonith->cmds->monitor = stonith_api_monitor;
1965 new_stonith->cmds->status = stonith_api_status;
1966 new_stonith->cmds->fence = stonith_api_fence;
1967 new_stonith->cmds->fence_with_delay = stonith_api_fence_with_delay;
1968 new_stonith->cmds->confirm = stonith_api_confirm;
1969 new_stonith->cmds->history = stonith_api_history;
1970
1971 new_stonith->cmds->list_agents = stonith_api_device_list;
1972 new_stonith->cmds->metadata = stonith_api_device_metadata;
1973
1974 new_stonith->cmds->query = stonith_api_query;
1975 new_stonith->cmds->remove_device = stonith_api_remove_device;
1976 new_stonith->cmds->register_device = stonith_api_register_device;
1977
1978 new_stonith->cmds->remove_level = stonith_api_remove_level;
1979 new_stonith->cmds->remove_level_full = stonith_api_remove_level_full;
1980 new_stonith->cmds->register_level = stonith_api_register_level;
1981 new_stonith->cmds->register_level_full = stonith_api_register_level_full;
1982
1983 new_stonith->cmds->remove_callback = stonith_api_del_callback;
1984 new_stonith->cmds->register_callback = stonith_api_add_callback;
1985 new_stonith->cmds->remove_notification = stonith_api_del_notification;
1986 new_stonith->cmds->register_notification = stonith_api_add_notification;
1987
1988 new_stonith->cmds->validate = stonith_api_validate;
1989
1990 return new_stonith;
1991 }
1992
1993 /*!
1994 * \internal
1995 * \brief Free a fencer API connection object
1996 *
1997 * \param[in,out] stonith_api Fencer API connection object
1998 */
1999 void
2000 stonith__api_free(stonith_t *stonith_api)
2001 {
2002 pcmk__trace("Destroying %p", stonith_api);
2003 if (stonith_api != NULL) {
2004 stonith_api->cmds->free(stonith_api);
2005 }
2006 }
2007
2008 /*!
2009 * \internal
2010 * \brief Connect to the fencer, retrying on failure
2011 *
2012 * \param[in,out] stonith Fencer API connection object
2013 * \param[in] name Client name to use with fencer
2014 * \param[in] max_attempts Maximum number of attempts
2015 *
2016 * \return \c pcmk_rc_ok on success, or result of last attempt otherwise
2017 */
2018 int
2019 stonith__api_connect_retry(stonith_t *stonith_api, const char *name,
2020 int max_attempts)
2021 {
2022 int rc = EINVAL; // if max_attempts is not positive
2023
2024 for (int attempt = 1; attempt <= max_attempts; attempt++) {
2025 rc = stonith_api->cmds->connect(stonith_api, name, NULL);
2026 rc = pcmk_legacy2rc(rc);
2027
2028 if (rc == pcmk_rc_ok) {
2029 return rc;
2030 }
2031 if (attempt < max_attempts) {
2032 pcmk__notice("Fencer connection attempt %d of %d failed "
2033 "(retrying in 2s): %s " QB_XS " rc=%d",
2034 attempt, max_attempts, pcmk_rc_str(rc), rc);
2035 sleep(2);
2036 }
2037 }
2038 pcmk__notice("Could not connect to fencer: %s " QB_XS " rc=%d",
2039 pcmk_rc_str(rc), rc);
2040 return rc;
2041 }
2042
2043 /*!
2044 * \internal
2045 * \brief Append a newly allocated STONITH key-value pair to a list
2046 *
2047 * \param[in,out] head Head of key-value pair list (\c NULL for new list)
2048 * \param[in] key Key to add
2049 * \param[in] value Value to add
2050 *
2051 * \return Head of appended-to list (equal to \p head if \p head is not \c NULL)
2052 * \note The caller is responsible for freeing the return value using
2053 * \c stonith__key_value_freeall().
2054 */
2055 stonith_key_value_t *
2056 stonith__key_value_add(stonith_key_value_t *head, const char *key,
2057 const char *value)
2058 {
2059 /* @COMPAT Replace this function with pcmk_prepend_nvpair(), and reverse the
2060 * list when finished adding to it; or with a hash table where order does
2061 * not matter
2062 */
2063 stonith_key_value_t *pair = pcmk__assert_alloc(1,
2064 sizeof(stonith_key_value_t));
2065
2066 pair->key = pcmk__str_copy(key);
2067 pair->value = pcmk__str_copy(value);
2068
2069 if (head != NULL) {
2070 stonith_key_value_t *end = head;
2071
2072 for (; end->next != NULL; end = end->next);
2073 end->next = pair;
2074
2075 } else {
2076 head = pair;
2077 }
2078
2079 return head;
2080 }
2081
2082 /*!
2083 * \internal
2084 * \brief Free all items in a \c stonith_key_value_t list
2085 *
2086 * This means freeing the list itself with all of its nodes. Keys and values may
2087 * be freed depending on arguments.
2088 *
2089 * \param[in,out] head Head of list
2090 * \param[in] keys If \c true, free all keys
2091 * \param[in] values If \c true, free all values
2092 */
2093 void
2094 stonith__key_value_freeall(stonith_key_value_t *head, bool keys, bool values)
2095 {
2096 while (head != NULL) {
2097 stonith_key_value_t *next = head->next;
2098
2099 if (keys) {
2100 free(head->key);
2101 }
2102 if (values) {
2103 free(head->value);
2104 }
2105 free(head);
2106 head = next;
2107 }
2108 }
2109
2110 #define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON)
2111 #define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __func__, args)
2112
2113 int
2114 stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off)
2115 {
2116 int rc = pcmk_ok;
2117 stonith_t *st = stonith__api_new();
2118 const char *action = off? PCMK_ACTION_OFF : PCMK_ACTION_REBOOT;
2119
2120 api_log_open();
2121 if (st == NULL) {
2122 api_log(LOG_ERR, "API initialization failed, could not kick (%s) node %u/%s",
2123 action, nodeid, uname);
2124 return -EPROTO;
2125 }
2126
2127 rc = st->cmds->connect(st, "stonith-api", NULL);
2128 if (rc != pcmk_ok) {
2129 api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)",
2130 action, nodeid, uname, pcmk_strerror(rc), rc);
2131 } else {
2132 char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
2133 int opts = 0;
2134
2135 stonith__set_call_options(opts, name,
2136 st_opt_sync_call|st_opt_allow_self_fencing);
2137 if ((uname == NULL) && (nodeid > 0)) {
2138 stonith__set_call_options(opts, name, st_opt_cs_nodeid);
2139 }
2140 rc = st->cmds->fence(st, opts, name, action, timeout, 0);
2141 free(name);
2142
2143 if (rc != pcmk_ok) {
2144 api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)",
2145 action, nodeid, uname, pcmk_strerror(rc), rc);
2146 } else {
2147 api_log(LOG_NOTICE, "Node %u/%s kicked: %s", nodeid, uname, action);
2148 }
2149 }
2150
2151 stonith__api_free(st);
2152 return rc;
2153 }
2154
2155 time_t
2156 stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress)
2157 {
2158 int rc = pcmk_ok;
2159 time_t when = 0;
2160 stonith_t *st = stonith__api_new();
2161 stonith_history_t *history = NULL, *hp = NULL;
2162
2163 if (st == NULL) {
2164 api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: "
2165 "API initialization failed", nodeid, uname);
2166 return when;
2167 }
2168
2169 rc = st->cmds->connect(st, "stonith-api", NULL);
2170 if (rc != pcmk_ok) {
2171 api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc);
2172 } else {
2173 int entries = 0;
2174 int progress = 0;
2175 int completed = 0;
2176 int opts = 0;
2177 char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
2178
2179 stonith__set_call_options(opts, name, st_opt_sync_call);
2180 if ((uname == NULL) && (nodeid > 0)) {
2181 stonith__set_call_options(opts, name, st_opt_cs_nodeid);
2182 }
2183 rc = st->cmds->history(st, opts, name, &history, 120);
2184 free(name);
2185
2186 for (hp = history; hp; hp = hp->next) {
2187 entries++;
2188 if (in_progress) {
2189 progress++;
2190 if (hp->state != st_done && hp->state != st_failed) {
2191 when = time(NULL);
2192 }
2193
2194 } else if (hp->state == st_done) {
2195 completed++;
2196 if (hp->completed > when) {
2197 when = hp->completed;
2198 }
2199 }
2200 }
2201
2202 stonith__history_free(history);
2203
2204 if(rc == pcmk_ok) {
2205 api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed);
2206 } else {
2207 api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc);
2208 }
2209 }
2210
2211 stonith__api_free(st);
2212
2213 if(when) {
2214 api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when);
2215 }
2216 return when;
2217 }
2218
2219 /*!
2220 * \internal
2221 * \brief Check whether a fence agent with a given name exists
2222 *
2223 * \param[in] name Agent name
2224 *
2225 * \retval \c true If a fence agent named \p name exists
2226 * \retval \c false Otherwise
2227 */
2228 bool
2229 stonith__agent_exists(const char *name)
2230 {
2231 stonith_t *stonith_api = NULL;
2232 stonith_key_value_t *agents = NULL;
2233 bool rc = false;
2234
2235 if (name == NULL) {
2236 return false;
2237 }
2238
2239 stonith_api = stonith__api_new();
2240 if (stonith_api == NULL) {
2241 pcmk__err("Could not list fence agents: API memory allocation failed");
2242 return false;
2243 }
2244
2245 // The list_agents method ignores its timeout argument
2246 stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL, &agents,
2247 0);
2248
2249 for (const stonith_key_value_t *iter = agents; iter != NULL;
2250 iter = iter->next) {
2251 if (pcmk__str_eq(iter->value, name, pcmk__str_none)) {
2252 rc = true;
2253 break;
2254 }
2255 }
2256
2257 stonith__key_value_freeall(agents, true, true);
2258 stonith__api_free(stonith_api);
2259 return rc;
2260 }
2261
2262 /*!
2263 * \internal
2264 * \brief Parse a target name from one line of a target list string
2265 *
2266 * \param[in] line One line of a target list string
2267 * \param[in] len String length of line
2268 * \param[in,out] output List to add newly allocated target name to
2269 */
2270 static void
2271 parse_list_line(const char *line, int len, GList **output)
2272 {
2273 size_t i = 0;
2274 size_t entry_start = 0;
2275
2276 if (line == NULL) {
2277 return;
2278 }
2279
2280 /* Skip complaints about additional parameters device doesn't understand
2281 *
2282 * @TODO Document or eliminate the implied restriction of target names
2283 */
2284 if (strstr(line, "invalid") || strstr(line, "variable")) {
2285 pcmk__debug("Skipping list output line: %s", line);
2286 return;
2287 }
2288
2289 // Process line content, character by character
2290 for (i = 0; i <= len; i++) {
2291
2292 if (isspace(line[i]) || (line[i] == ',') || (line[i] == ';')
2293 || (line[i] == '\0')) {
2294 // We've found a separator (i.e. the end of an entry)
2295
2296 int rc = 0;
2297 char *entry = NULL;
2298
2299 if (i == entry_start) {
2300 // Skip leading and sequential separators
2301 entry_start = i + 1;
2302 continue;
2303 }
2304
2305 entry = pcmk__assert_alloc(i - entry_start + 1, sizeof(char));
2306
2307 /* Read entry, stopping at first separator
2308 *
2309 * @TODO Document or eliminate these character restrictions
2310 */
2311 rc = sscanf(line + entry_start, "%[a-zA-Z0-9_-.]", entry);
2312 if (rc != 1) {
2313 pcmk__warn("Could not parse list output entry: %s "
2314 QB_XS " entry_start=%d position=%d",
2315 (line + entry_start), entry_start, i);
2316 free(entry);
2317
2318 } else if (pcmk__strcase_any_of(entry, PCMK_ACTION_ON,
2319 PCMK_ACTION_OFF, NULL)) {
2320 /* Some agents print the target status in the list output,
2321 * though none are known now (the separate list-status command
2322 * is used for this, but it can also print "UNKNOWN"). To handle
2323 * this possibility, skip such entries.
2324 *
2325 * @TODO Document or eliminate the implied restriction of target
2326 * names.
2327 */
2328 free(entry);
2329
2330 } else {
2331 // We have a valid entry
2332 *output = g_list_append(*output, entry);
2333 }
2334 entry_start = i + 1;
2335 }
2336 }
2337 }
2338
2339 /*!
2340 * \internal
2341 * \brief Parse a list of targets from a string
2342 *
2343 * \param[in] list_output Target list as a string
2344 *
2345 * \return List of target names
2346 * \note The target list string format is flexible, to allow for user-specified
2347 * lists such pcmk_host_list and the output of an agent's list action
2348 * (whether direct or via the API, which escapes newlines). There may be
2349 * multiple lines, separated by either a newline or an escaped newline
2350 * (backslash n). Each line may have one or more target names, separated
2351 * by any combination of whitespace, commas, and semi-colons. Lines
2352 * containing "invalid" or "variable" will be ignored entirely. Target
2353 * names "on" or "off" (case-insensitive) will be ignored. Target names
2354 * may contain only alphanumeric characters, underbars (_), dashes (-),
2355 * and dots (.) (if any other character occurs in the name, it and all
2356 * subsequent characters in the name will be ignored).
2357 * \note The caller is responsible for freeing the result with
2358 * g_list_free_full(result, free).
2359 */
2360 GList *
2361 stonith__parse_targets(const char *target_spec)
2362 {
2363 GList *targets = NULL;
2364
2365 if (target_spec != NULL) {
2366 size_t out_len = strlen(target_spec);
2367 size_t line_start = 0; // Starting index of line being processed
2368
2369 for (size_t i = 0; i <= out_len; ++i) {
2370 if ((target_spec[i] == '\n') || (target_spec[i] == '\0')
2371 || ((target_spec[i] == '\\') && (target_spec[i + 1] == 'n'))) {
2372 // We've reached the end of one line of output
2373
2374 int len = i - line_start;
2375
2376 if (len > 0) {
2377 char *line = strndup(target_spec + line_start, len);
2378
2379 pcmk__assert(line != NULL);
2380
2381 // cppcheck-suppress nullPointerOutOfMemory
2382 line[len] = '\0'; // Because it might be a newline
2383 parse_list_line(line, len, &targets);
2384 free(line);
2385 }
2386 if (target_spec[i] == '\\') {
2387 ++i; // backslash-n takes up two positions
2388 }
2389 line_start = i + 1;
2390 }
2391 }
2392 }
2393 return targets;
2394 }
2395
2396 /*!
2397 * \internal
2398 * \brief Check whether a fencing failure was followed by an equivalent success
2399 *
2400 * \param[in] event Fencing failure
2401 * \param[in] top_history Complete fencing history (must be sorted by
2402 * stonith__sort_history() beforehand)
2403 *
2404 * \return The name of the node that executed the fencing if a later successful
2405 * event exists, or NULL if no such event exists
2406 */
2407 const char *
2408 stonith__later_succeeded(const stonith_history_t *event,
2409 const stonith_history_t *top_history)
2410 {
2411 const char *other = NULL;
2412
2413 for (const stonith_history_t *prev_hp = top_history;
2414 prev_hp != NULL; prev_hp = prev_hp->next) {
2415 if (prev_hp == event) {
2416 break;
2417 }
2418 if ((prev_hp->state == st_done) &&
2419 pcmk__str_eq(event->target, prev_hp->target, pcmk__str_casei) &&
2420 pcmk__str_eq(event->action, prev_hp->action, pcmk__str_none) &&
2421 ((event->completed < prev_hp->completed) ||
2422 ((event->completed == prev_hp->completed) && (event->completed_nsec < prev_hp->completed_nsec)))) {
2423
2424 if ((event->delegate == NULL)
2425 || pcmk__str_eq(event->delegate, prev_hp->delegate,
2426 pcmk__str_casei)) {
2427 // Prefer equivalent fencing by same executioner
2428 return prev_hp->delegate;
2429
2430 } else if (other == NULL) {
2431 // Otherwise remember first successful executioner
2432 other = (prev_hp->delegate == NULL)? "some node" : prev_hp->delegate;
2433 }
2434 }
2435 }
2436 return other;
2437 }
2438
2439 /*!
2440 * \internal
2441 * \brief Sort fencing history, pending first then by most recently completed
2442 *
2443 * \param[in,out] history List of fencing actions
2444 *
2445 * \return New head of sorted \p history
2446 */
2447 stonith_history_t *
2448 stonith__sort_history(stonith_history_t *history)
2449 {
2450 stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
2451
2452 for (hp = history; hp; ) {
2453 tmp = hp->next;
2454 if ((hp->state == st_done) || (hp->state == st_failed)) {
2455 /* sort into new */
2456 if ((!new) || (hp->completed > new->completed) ||
2457 ((hp->completed == new->completed) && (hp->completed_nsec > new->completed_nsec))) {
2458 hp->next = new;
2459 new = hp;
2460 } else {
2461 np = new;
2462 do {
2463 if ((!np->next) || (hp->completed > np->next->completed) ||
2464 ((hp->completed == np->next->completed) && (hp->completed_nsec > np->next->completed_nsec))) {
2465 hp->next = np->next;
2466 np->next = hp;
2467 break;
2468 }
2469 np = np->next;
2470 } while (1);
2471 }
2472 } else {
2473 /* put into pending */
2474 hp->next = pending;
2475 pending = hp;
2476 }
2477 hp = tmp;
2478 }
2479
2480 /* pending actions don't have a completed-stamp so make them go front */
2481 if (pending) {
2482 stonith_history_t *last_pending = pending;
2483
2484 while (last_pending->next) {
2485 last_pending = last_pending->next;
2486 }
2487
2488 last_pending->next = new;
2489 new = pending;
2490 }
2491 return new;
2492 }
2493
2494 /*!
2495 * \internal
2496 * \brief Return string equivalent of a fencing operation state value
2497 *
2498 * \param[in] state Fencing operation state value
2499 *
2500 * \return Human-friendly string equivalent of \p state
2501 */
2502 const char *
2503 stonith__op_state_text(enum op_state state)
2504 {
2505 // @COMPAT Move this to the fencer after dropping stonith_op_state_str()
2506 switch (state) {
2507 case st_query:
2508 return "querying";
2509 case st_exec:
2510 return "executing";
2511 case st_done:
2512 return "completed";
2513 case st_duplicate:
2514 return "duplicate";
2515 case st_failed:
2516 return "failed";
2517 default:
2518 return "unknown";
2519 }
2520 }
2521
2522 stonith_history_t *
2523 stonith__first_matching_event(stonith_history_t *history,
2524 bool (*matching_fn)(stonith_history_t *, void *),
2525 void *user_data)
2526 {
2527 for (stonith_history_t *hp = history; hp; hp = hp->next) {
2528 if (matching_fn(hp, user_data)) {
2529 return hp;
2530 }
2531 }
2532
2533 return NULL;
2534 }
2535
2536 bool
2537 stonith__event_state_pending(stonith_history_t *history, void *user_data)
2538 {
2539 return history->state != st_failed && history->state != st_done;
2540 }
2541
2542 bool
2543 stonith__event_state_eq(stonith_history_t *history, void *user_data)
2544 {
2545 return history->state == GPOINTER_TO_INT(user_data);
2546 }
2547
2548 bool
2549 stonith__event_state_neq(stonith_history_t *history, void *user_data)
2550 {
2551 return history->state != GPOINTER_TO_INT(user_data);
2552 }
2553
2554 /*!
2555 * \internal
2556 * \brief Check whether a given parameter exists in a fence agent's metadata
2557 *
2558 * \param[in] metadata Agent metadata
2559 * \param[in] name Parameter name
2560 *
2561 * \retval \c true If \p name exists as a parameter in \p metadata
2562 * \retval \c false Otherwise
2563 */
2564 static bool
2565 param_is_supported(xmlNode *metadata, const char *name)
2566 {
2567 char *xpath_s = pcmk__assert_asprintf("//" PCMK_XE_PARAMETER
2568 "[@" PCMK_XA_NAME "='%s']",
2569 name);
2570 xmlXPathObject *xpath = pcmk__xpath_search(metadata->doc, xpath_s);
2571 bool supported = (pcmk__xpath_num_results(xpath) > 0);
2572
2573 free(xpath_s);
2574 xmlXPathFreeObject(xpath);
2575 return supported;
2576 }
2577
2578 /*!
2579 * \internal
2580 * \brief Get the default host argument based on a device's agent metadata
2581 *
2582 * If an agent supports the "plug" parameter, default to that. Otherwise default
2583 * to the "port" parameter if supported. Otherwise return \c NULL.
2584 *
2585 * \param[in] metadata Agent metadata
2586 *
2587 * \return Parameter name for default host argument
2588 */
2589 const char *
2590 stonith__default_host_arg(xmlNode *metadata)
2591 {
2592 CRM_CHECK(metadata != NULL, return NULL);
2593
2594 if (param_is_supported(metadata, "plug")) {
2595 return "plug";
2596 }
2597 if (param_is_supported(metadata, "port")) {
2598 return "port";
2599 }
2600 return NULL;
2601 }
2602
2603 /*!
2604 * \internal
2605 * \brief Retrieve fence agent meta-data asynchronously
2606 *
2607 * \param[in] agent Agent to execute
2608 * \param[in] timeout_sec Error if not complete within this time
2609 * \param[in] callback Function to call with result (this will always be
2610 * called, whether by this function directly or
2611 * later via the main loop, and on success the
2612 * metadata will be in its result argument's
2613 * action_stdout)
2614 * \param[in,out] user_data User data to pass to callback
2615 *
2616 * \return Standard Pacemaker return code
2617 * \note The caller must use a main loop. This function is not a
2618 * stonith_api_operations_t method because it does not need a stonith_t
2619 * object and does not go through the fencer, but executes the agent
2620 * directly.
2621 */
2622 int
2623 stonith__metadata_async(const char *agent, int timeout_sec,
2624 void (*callback)(int pid,
2625 const pcmk__action_result_t *result,
2626 void *user_data),
2627 void *user_data)
2628 {
2629 switch (get_namespace_from_agent(agent)) {
2630 case st_namespace_rhcs:
2631 {
2632 stonith_action_t *action = NULL;
2633 int rc = pcmk_ok;
2634
2635 action = stonith__action_create(agent, PCMK_ACTION_METADATA,
2636 NULL, timeout_sec, NULL, NULL,
2637 NULL);
2638
2639 rc = stonith__execute_async(action, user_data, callback, NULL);
2640 if (rc != pcmk_ok) {
2641 callback(0, stonith__action_result(action), user_data);
2642 stonith__destroy_action(action);
2643 }
2644 return pcmk_legacy2rc(rc);
2645 }
2646
2647 #if HAVE_STONITH_STONITH_H
2648 case st_namespace_lha:
2649 // LHA metadata is simply synthesized, so simulate async
2650 {
2651 pcmk__action_result_t result = {
2652 .exit_status = CRM_EX_OK,
2653 .execution_status = PCMK_EXEC_DONE,
2654 .exit_reason = NULL,
2655 .action_stdout = NULL,
2656 .action_stderr = NULL,
2657 };
2658
2659 stonith__lha_metadata(agent, timeout_sec,
2660 &result.action_stdout);
2661 callback(0, &result, user_data);
2662 pcmk__reset_result(&result);
2663 return pcmk_rc_ok;
2664 }
2665 #endif
2666
2667 default:
2668 {
2669 pcmk__action_result_t result = {
2670 .exit_status = CRM_EX_NOSUCH,
2671 .execution_status = PCMK_EXEC_ERROR_HARD,
2672 .exit_reason = pcmk__assert_asprintf("No such agent '%s'",
2673 agent),
2674 .action_stdout = NULL,
2675 .action_stderr = NULL,
2676 };
2677
2678 callback(0, &result, user_data);
2679 pcmk__reset_result(&result);
2680 return ENOENT;
2681 }
2682 }
2683 }
2684
2685 /*!
2686 * \internal
2687 * \brief Return the exit status from an async action callback
2688 *
2689 * \param[in] data Callback data
2690 *
2691 * \return Exit status from callback data
2692 */
2693 int
2694 stonith__exit_status(const stonith_callback_data_t *data)
2695 {
2696 if ((data == NULL) || (data->opaque == NULL)) {
2697 return CRM_EX_ERROR;
2698 }
2699 return ((pcmk__action_result_t *) data->opaque)->exit_status;
2700 }
2701
2702 /*!
2703 * \internal
2704 * \brief Return the execution status from an async action callback
2705 *
2706 * \param[in] data Callback data
2707 *
2708 * \return Execution status from callback data
2709 */
2710 int
2711 stonith__execution_status(const stonith_callback_data_t *data)
2712 {
2713 if ((data == NULL) || (data->opaque == NULL)) {
2714 return PCMK_EXEC_UNKNOWN;
2715 }
2716 return ((pcmk__action_result_t *) data->opaque)->execution_status;
2717 }
2718
2719 /*!
2720 * \internal
2721 * \brief Return the exit reason from an async action callback
2722 *
2723 * \param[in] data Callback data
2724 *
2725 * \return Exit reason from callback data
2726 */
2727 const char *
2728 stonith__exit_reason(const stonith_callback_data_t *data)
2729 {
2730 if ((data == NULL) || (data->opaque == NULL)) {
2731 return NULL;
2732 }
2733 return ((pcmk__action_result_t *) data->opaque)->exit_reason;
2734 }
2735
2736 /*!
2737 * \internal
2738 * \brief Return the exit status from an event notification
2739 *
2740 * \param[in] event Event
2741 *
2742 * \return Exit status from event
2743 */
2744 int
2745 stonith__event_exit_status(const stonith_event_t *event)
2746 {
2747 if ((event == NULL) || (event->opaque == NULL)) {
2748 return CRM_EX_ERROR;
2749 } else {
2750 struct event_private *event_private = event->opaque;
2751
2752 return event_private->result.exit_status;
2753 }
2754 }
2755
2756 /*!
2757 * \internal
2758 * \brief Return the execution status from an event notification
2759 *
2760 * \param[in] event Event
2761 *
2762 * \return Execution status from event
2763 */
2764 int
2765 stonith__event_execution_status(const stonith_event_t *event)
2766 {
2767 if ((event == NULL) || (event->opaque == NULL)) {
2768 return PCMK_EXEC_UNKNOWN;
2769 } else {
2770 struct event_private *event_private = event->opaque;
2771
2772 return event_private->result.execution_status;
2773 }
2774 }
2775
2776 /*!
2777 * \internal
2778 * \brief Return the exit reason from an event notification
2779 *
2780 * \param[in] event Event
2781 *
2782 * \return Exit reason from event
2783 */
2784 const char *
2785 stonith__event_exit_reason(const stonith_event_t *event)
2786 {
2787 if ((event == NULL) || (event->opaque == NULL)) {
2788 return NULL;
2789 } else {
2790 struct event_private *event_private = event->opaque;
2791
2792 return event_private->result.exit_reason;
2793 }
2794 }
2795
2796 /*!
2797 * \internal
2798 * \brief Return a human-friendly description of a fencing event
2799 *
2800 * \param[in] event Event to describe
2801 *
2802 * \return Newly allocated string with description of \p event
2803 * \note The caller is responsible for freeing the return value.
2804 * This function asserts on memory errors and never returns NULL.
2805 */
2806 char *
2807 stonith__event_description(const stonith_event_t *event)
2808 {
2809 // Use somewhat readable defaults
2810 const char *origin = pcmk__s(event->client_origin, "a client");
2811 const char *origin_node = pcmk__s(event->origin, "a node");
2812 const char *executioner = pcmk__s(event->executioner, "the cluster");
2813 const char *device = pcmk__s(event->device, "unknown");
2814 const char *action = pcmk__s(event->action, event->operation);
2815 const char *target = pcmk__s(event->target, "no node");
2816 const char *reason = stonith__event_exit_reason(event);
2817 const char *status;
2818
2819 if (action == NULL) {
2820 action = "(unknown)";
2821 }
2822
2823 if (stonith__event_execution_status(event) != PCMK_EXEC_DONE) {
2824 status = pcmk_exec_status_str(stonith__event_execution_status(event));
2825 } else if (stonith__event_exit_status(event) != CRM_EX_OK) {
2826 status = pcmk_exec_status_str(PCMK_EXEC_ERROR);
2827 } else {
2828 status = crm_exit_str(CRM_EX_OK);
2829 }
2830
2831 if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_HISTORY,
2832 pcmk__str_none)) {
2833 return pcmk__assert_asprintf("Fencing history may have changed");
2834
2835 } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_ADD,
2836 pcmk__str_none)) {
2837 return pcmk__assert_asprintf("A fencing device (%s) was added", device);
2838
2839 } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_DEL,
2840 pcmk__str_none)) {
2841 return pcmk__assert_asprintf("A fencing device (%s) was removed",
2842 device);
2843
2844 } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_ADD,
2845 pcmk__str_none)) {
2846 return pcmk__assert_asprintf("A fencing topology level (%s) was added",
2847 device);
2848
2849 } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_DEL,
2850 pcmk__str_none)) {
2851 return pcmk__assert_asprintf("A fencing topology level (%s) was "
2852 "removed",
2853 device);
2854 }
2855
2856 // event->operation should be PCMK__VALUE_ST_NOTIFY_FENCE at this point
2857
2858 return pcmk__assert_asprintf("Operation %s of %s by %s for %s@%s: %s%s%s%s "
2859 "(ref=%s)",
2860 action, target, executioner, origin,
2861 origin_node, status,
2862 ((reason == NULL)? "" : " ("),
2863 pcmk__s(reason, ""),
2864 ((reason == NULL)? "" : ")"),
2865 pcmk__s(event->id, "(none)"));
2866 }
2867
2868 // Deprecated functions kept only for backward API compatibility
2869 // LCOV_EXCL_START
2870
2871 // See comments in stonith-ng.h for why we re-declare before defining
2872
2873 stonith_t *stonith_api_new(void);
2874
2875 stonith_t *
2876 stonith_api_new(void)
2877 {
2878 return stonith__api_new();
2879 }
2880
2881 void stonith_api_delete(stonith_t *stonith);
2882
2883 void
2884 stonith_api_delete(stonith_t *stonith)
2885 {
2886 stonith__api_free(stonith);
2887 }
2888
2889 static void
2890 stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
2891 {
2892 int call = GPOINTER_TO_INT(key);
2893 stonith_callback_client_t *blob = value;
2894
2895 pcmk__debug("Call %d (%s): pending", call, pcmk__s(blob->id, "no ID"));
2896 }
2897
2898 void stonith_dump_pending_callbacks(stonith_t *stonith);
2899
2900 void
2901 stonith_dump_pending_callbacks(stonith_t *stonith)
2902 {
2903 stonith_private_t *private = stonith->st_private;
2904
2905 if (private->stonith_op_callback_table == NULL) {
2906 return;
2907 }
2908 return g_hash_table_foreach(private->stonith_op_callback_table,
2909 stonith_dump_pending_op, NULL);
2910 }
2911
2912 bool stonith_dispatch(stonith_t *stonith_api);
2913
2914 bool
2915 stonith_dispatch(stonith_t *stonith_api)
2916 {
2917 return (stonith__api_dispatch(stonith_api) == pcmk_rc_ok);
2918 }
2919
2920 stonith_key_value_t *stonith_key_value_add(stonith_key_value_t *head,
2921 const char *key, const char *value);
2922
2923 stonith_key_value_t *
2924 stonith_key_value_add(stonith_key_value_t *head, const char *key,
2925 const char *value)
2926 {
2927 return stonith__key_value_add(head, key, value);
2928 }
2929
2930 void stonith_key_value_freeall(stonith_key_value_t *head, int keys, int values);
2931
2932 void
2933 stonith_key_value_freeall(stonith_key_value_t *head, int keys, int values)
2934 {
2935 stonith__key_value_freeall(head, (keys != 0), (values != 0));
2936 }
2937
2938 void stonith_history_free(stonith_history_t *head);
2939
2940 void
2941 stonith_history_free(stonith_history_t *head)
2942 {
2943 stonith__history_free(head);
2944 }
2945
2946 int stonith_api_connect_retry(stonith_t *st, const char *name,
2947 int max_attempts);
2948
2949 int
2950 stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts)
2951 {
2952 return pcmk_rc2legacy(stonith__api_connect_retry(st, name, max_attempts));
2953 }
2954
2955 const char *stonith_op_state_str(enum op_state state);
2956
2957 const char *
2958 stonith_op_state_str(enum op_state state)
2959 {
2960 return stonith__op_state_text(state);
2961 }
2962
2963 bool stonith_agent_exists(const char *agent, int timeout);
2964
2965 bool
2966 stonith_agent_exists(const char *agent, int timeout)
2967 {
2968 return stonith__agent_exists(agent);
2969 }
2970
2971 const char *stonith_action_str(const char *action);
2972
2973 const char *
2974 stonith_action_str(const char *action)
2975 {
2976 if (action == NULL) {
2977 return "fencing";
2978 } else if (strcmp(action, PCMK_ACTION_ON) == 0) {
2979 return "unfencing";
2980 } else if (strcmp(action, PCMK_ACTION_OFF) == 0) {
2981 return "turning off";
2982 } else {
2983 return action;
2984 }
2985 }
2986
2987 enum stonith_namespace stonith_text2namespace(const char *namespace_s);
2988
2989 enum stonith_namespace
2990 stonith_text2namespace(const char *namespace_s)
2991 {
2992 return parse_namespace(namespace_s);
2993 }
2994
2995 const char *stonith_namespace2text(enum stonith_namespace st_namespace);
2996
2997 const char *
2998 stonith_namespace2text(enum stonith_namespace st_namespace)
2999 {
3000 return namespace_text(st_namespace);
3001 }
3002
3003 enum stonith_namespace stonith_get_namespace(const char *agent,
3004 const char *namespace_s);
3005
3006 enum stonith_namespace
3007 stonith_get_namespace(const char *agent, const char *namespace_s)
3008 {
3009 if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) {
3010 return st_namespace_internal;
3011 }
3012 return get_namespace_from_agent(agent);
3013 }
3014
3015 // LCOV_EXCL_STOP
3016 // End deprecated API
3017