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