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