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 <stdbool.h>
13   	#include <sys/param.h>
14   	#include <sys/stat.h>
15   	
16   	#include <crm/crm.h>
17   	#include <crm/common/xml.h>
18   	#include <crm/lrmd_internal.h>
19   	#include <pacemaker-internal.h>
20   	
21   	
22   	/*
23   	 * Functions for freeing transition graph objects
24   	 */
25   	
26   	/*!
27   	 * \internal
28   	 * \brief Free a transition graph action object
29   	 *
30   	 * \param[in,out] user_data  Action to free
31   	 */
32   	static void
33   	free_graph_action(gpointer user_data)
34   	{
35   	    pcmk__graph_action_t *action = user_data;
36   	
(1) Event path: Condition "action->timer != 0", taking true branch.
37   	    if (action->timer != 0) {
38   	        pcmk__warn("Cancelling timer for graph action %d", action->id);
39   	        g_source_remove(action->timer);
40   	    }
CID (unavailable; MK=0c25afa605d42f289cbe49c8a5eec78d) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(2) Event assign_union_field: The union field "in" of "_pp" is written.
(3) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
41   	    g_clear_pointer(&action->params, g_hash_table_destroy);
42   	    pcmk__xml_free(action->xml);
43   	    free(action);
44   	}
45   	
46   	/*!
47   	 * \internal
48   	 * \brief Free a transition graph synapse object
49   	 *
50   	 * \param[in,out] user_data  Synapse to free
51   	 */
52   	static void
53   	free_graph_synapse(gpointer user_data)
54   	{
55   	    pcmk__graph_synapse_t *synapse = user_data;
56   	
57   	    g_list_free_full(synapse->actions, free_graph_action);
58   	    g_list_free_full(synapse->inputs, free_graph_action);
59   	    free(synapse);
60   	}
61   	
62   	/*!
63   	 * \internal
64   	 * \brief Free a transition graph object
65   	 *
66   	 * \param[in,out] graph  Transition graph to free
67   	 */
68   	void
69   	pcmk__free_graph(pcmk__graph_t *graph)
70   	{
71   	    if (graph != NULL) {
72   	        g_list_free_full(graph->synapses, free_graph_synapse);
73   	        free(graph->source);
74   	        free(graph->failed_stop_offset);
75   	        free(graph->failed_start_offset);
76   	        free(graph);
77   	    }
78   	}
79   	
80   	
81   	/*
82   	 * Functions for updating graph
83   	 */
84   	
85   	/*!
86   	 * \internal
87   	 * \brief Update synapse after completed prerequisite
88   	 *
89   	 * A synapse is ready to be executed once all its prerequisite actions (inputs)
90   	 * complete. Given a completed action, check whether it is an input for a given
91   	 * synapse, and if so, mark the input as confirmed, and mark the synapse as
92   	 * ready if appropriate.
93   	 *
94   	 * \param[in,out] synapse    Transition graph synapse to update
95   	 * \param[in]     action_id  ID of an action that completed
96   	 *
97   	 * \note The only substantial effect here is confirming synapse inputs.
98   	 *       should_fire_synapse() will recalculate pcmk__synapse_ready, so the only
99   	 *       thing that uses the pcmk__synapse_ready from here is
100  	 *       synapse_state_str().
101  	 */
102  	static void
103  	update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id)
104  	{
105  	    if (pcmk__is_set(synapse->flags, pcmk__synapse_ready)) {
106  	        return; // All inputs have already been confirmed
107  	    }
108  	
109  	    // Presume ready until proven otherwise
110  	    pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
111  	
112  	    for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
113  	        pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
114  	
115  	        if (prereq->id == action_id) {
116  	            pcmk__trace("Confirming input %d of synapse %d", action_id,
117  	                        synapse->id);
118  	            pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed);
119  	
120  	        } else if (!pcmk__is_set(prereq->flags, pcmk__graph_action_confirmed)) {
121  	            pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
122  	            pcmk__trace("Synapse %d still not ready after action %d",
123  	                        synapse->id, action_id);
124  	        }
125  	    }
126  	    if (pcmk__is_set(synapse->flags, pcmk__synapse_ready)) {
127  	        pcmk__trace("Synapse %d is now ready to execute", synapse->id);
128  	    }
129  	}
130  	
131  	/*!
132  	 * \internal
133  	 * \brief Update action and synapse confirmation after action completion
134  	 *
135  	 * \param[in,out] synapse    Transition graph synapse that action belongs to
136  	 * \param[in]     action_id  ID of action that completed
137  	 */
138  	static void
139  	update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id)
140  	{
141  	    bool all_confirmed = true;
142  	
143  	    for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
144  	        pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
145  	
146  	        if (action->id == action_id) {
147  	            pcmk__trace("Confirmed action %d of synapse %d", action_id,
148  	                        synapse->id);
149  	            pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
150  	
151  	        } else if (all_confirmed
152  	                   && !pcmk__is_set(action->flags,
153  	                                    pcmk__graph_action_confirmed)) {
154  	            all_confirmed = false;
155  	            pcmk__trace("Synapse %d still not confirmed after action %d",
156  	                        synapse->id, action_id);
157  	        }
158  	    }
159  	
160  	    if (all_confirmed
161  	        && !pcmk__is_set(synapse->flags, pcmk__synapse_confirmed)) {
162  	        pcmk__trace("Confirmed synapse %d", synapse->id);
163  	        pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
164  	    }
165  	}
166  	
167  	/*!
168  	 * \internal
169  	 * \brief Update the transition graph with a completed action result
170  	 *
171  	 * \param[in,out] graph   Transition graph to update
172  	 * \param[in]     action  Action that completed
173  	 */
174  	void
175  	pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action)
176  	{
177  	    for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
178  	        pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
179  	
180  	        if (pcmk__any_flags_set(synapse->flags,
181  	                                pcmk__synapse_confirmed|pcmk__synapse_failed)) {
182  	            continue; // This synapse already completed
183  	
184  	        } else if (pcmk__is_set(synapse->flags, pcmk__synapse_executed)) {
185  	            update_synapse_confirmed(synapse, action->id);
186  	
187  	        } else if (!pcmk__is_set(action->flags, pcmk__graph_action_failed)
188  	                   || (synapse->priority == PCMK_SCORE_INFINITY)) {
189  	            update_synapse_ready(synapse, action->id);
190  	        }
191  	    }
192  	}
193  	
194  	
195  	/*
196  	 * Functions for executing graph
197  	 */
198  	
199  	/* A transition graph consists of various types of actions. The library caller
200  	 * registers execution functions for each action type, which will be stored
201  	 * here.
202  	 */
203  	static pcmk__graph_functions_t *graph_fns = NULL;
204  	
205  	/*!
206  	 * \internal
207  	 * \brief Set transition graph execution functions
208  	 *
209  	 * \param[in]  Execution functions to use
210  	 */
211  	void
212  	pcmk__set_graph_functions(pcmk__graph_functions_t *fns)
213  	{
214  	
215  	    pcmk__assert((fns != NULL) && (fns->rsc != NULL) && (fns->cluster != NULL)
216  	                 && (fns->pseudo != NULL) && (fns->fence != NULL));
217  	    pcmk__debug("Setting custom functions for executing transition graphs");
218  	    graph_fns = fns;
219  	}
220  	
221  	/*!
222  	 * \internal
223  	 * \brief Check whether a graph synapse is ready to be executed
224  	 *
225  	 * \param[in,out] graph    Transition graph that synapse is part of
226  	 * \param[in,out] synapse  Synapse to check
227  	 *
228  	 * \return true if synapse is ready, false otherwise
229  	 */
230  	static bool
231  	should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
232  	{
233  	    GList *lpc = NULL;
234  	
235  	    pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
236  	    for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
237  	        pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
238  	
239  	        if (!(pcmk__is_set(prereq->flags, pcmk__graph_action_confirmed))) {
240  	            pcmk__trace("Input %d for synapse %d not yet confirmed", prereq->id,
241  	                        synapse->id);
242  	            pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
243  	            break;
244  	
245  	        } else if (pcmk__is_set(prereq->flags, pcmk__graph_action_failed)) {
246  	            pcmk__trace("Input %d for synapse %d confirmed but failed",
247  	                        prereq->id, synapse->id);
248  	            pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
249  	            break;
250  	        }
251  	    }
252  	    if (pcmk__is_set(synapse->flags, pcmk__synapse_ready)) {
253  	        pcmk__trace("Synapse %d is ready to execute", synapse->id);
254  	    } else {
255  	        return false;
256  	    }
257  	
258  	    for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
259  	        pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data;
260  	
261  	        if (a->type == pcmk__pseudo_graph_action) {
262  	            /* None of the below applies to pseudo ops */
263  	
264  	        } else if (synapse->priority < graph->abort_priority) {
265  	            pcmk__trace("Skipping synapse %d: priority %d is less than abort "
266  	                        "priority %d",
267  	                        synapse->id, synapse->priority, graph->abort_priority);
268  	            graph->skipped++;
269  	            return false;
270  	
271  	        } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) {
272  	            pcmk__trace("Deferring synapse %d: not allowed", synapse->id);
273  	            return false;
274  	        }
275  	    }
276  	
277  	    return true;
278  	}
279  	
280  	/*!
281  	 * \internal
282  	 * \brief Initiate an action from a transition graph
283  	 *
284  	 * \param[in,out] graph   Transition graph containing action
285  	 * \param[in,out] action  Action to execute
286  	 *
287  	 * \return Standard Pacemaker return code
288  	 */
289  	static int
290  	initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
291  	{
292  	    const char *id = pcmk__xe_id(action->xml);
293  	
294  	    CRM_CHECK(id != NULL, return EINVAL);
295  	    CRM_CHECK(!pcmk__is_set(action->flags, pcmk__graph_action_executed),
296  	              return pcmk_rc_already);
297  	
298  	    pcmk__set_graph_action_flags(action, pcmk__graph_action_executed);
299  	    switch (action->type) {
300  	        case pcmk__pseudo_graph_action:
301  	            pcmk__trace("Executing pseudo-action %d (%s)", action->id, id);
302  	            return graph_fns->pseudo(graph, action);
303  	
304  	        case pcmk__rsc_graph_action:
305  	            pcmk__trace("Executing resource action %d (%s)", action->id, id);
306  	            return graph_fns->rsc(graph, action);
307  	
308  	        case pcmk__cluster_graph_action:
309  	            if (pcmk__str_eq(pcmk__xe_get(action->xml, PCMK_XA_OPERATION),
310  	                             PCMK_ACTION_STONITH, pcmk__str_none)) {
311  	                pcmk__trace("Executing fencing action %d (%s)", action->id, id);
312  	                return graph_fns->fence(graph, action);
313  	            }
314  	            pcmk__trace("Executing cluster action %d (%s)", action->id, id);
315  	            return graph_fns->cluster(graph, action);
316  	
317  	        default:
318  	            pcmk__err("Unsupported graph action type <%s " PCMK_XA_ID "='%s'> "
319  	                      "(bug?)",
320  	                      action->xml->name, id);
321  	            return EINVAL;
322  	    }
323  	}
324  	
325  	/*!
326  	 * \internal
327  	 * \brief Execute a graph synapse
328  	 *
329  	 * \param[in,out] graph    Transition graph with synapse to execute
330  	 * \param[in,out] synapse  Synapse to execute
331  	 *
332  	 * \return Standard Pacemaker return value
333  	 */
334  	static int
335  	fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
336  	{
337  	    pcmk__set_synapse_flags(synapse, pcmk__synapse_executed);
338  	    for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
339  	        pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
340  	        int rc = initiate_action(graph, action);
341  	
342  	        if (rc != pcmk_rc_ok) {
343  	            pcmk__err("Failed initiating <%s " PCMK_XA_ID "=%d> in synapse %d: "
344  	                      "%s",
345  	                      action->xml->name, action->id, synapse->id,
346  	                      pcmk_rc_str(rc));
347  	            pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
348  	            pcmk__set_graph_action_flags(action,
349  	                                         pcmk__graph_action_confirmed
350  	                                         |pcmk__graph_action_failed);
351  	            return pcmk_rc_error;
352  	        }
353  	    }
354  	    return pcmk_rc_ok;
355  	}
356  	
357  	/*!
358  	 * \internal
359  	 * \brief Dummy graph method that can be used with simulations
360  	 *
361  	 * \param[in,out] graph   Transition graph containing action
362  	 * \param[in,out] action  Graph action to be initiated
363  	 *
364  	 * \return Standard Pacemaker return code
365  	 * \note If the PE_fail environment variable is set to the action ID,
366  	 *       then the graph action will be marked as failed.
367  	 */
368  	static int
369  	pseudo_action_dummy(pcmk__graph_t *graph, pcmk__graph_action_t *action)
370  	{
371  	    static int fail = -1;
372  	
373  	    if (fail < 0) {
374  	        long long fail_ll;
375  	
376  	        if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok)
377  	            && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) {
378  	            fail = (int) fail_ll;
379  	        } else {
380  	            fail = 0;
381  	        }
382  	    }
383  	
384  	    if (action->id == fail) {
385  	        pcmk__err("Dummy event handler: pretending action %d failed",
386  	                  action->id);
387  	        pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
388  	        graph->abort_priority = PCMK_SCORE_INFINITY;
389  	    } else {
390  	        pcmk__trace("Dummy event handler: action %d initiated", action->id);
391  	    }
392  	    pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
393  	    pcmk__update_graph(graph, action);
394  	    return pcmk_rc_ok;
395  	}
396  	
397  	static pcmk__graph_functions_t default_fns = {
398  	    pseudo_action_dummy,
399  	    pseudo_action_dummy,
400  	    pseudo_action_dummy,
401  	    pseudo_action_dummy
402  	};
403  	
404  	/*!
405  	 * \internal
406  	 * \brief Execute all actions in a transition graph
407  	 *
408  	 * \param[in,out] graph  Transition graph to execute
409  	 *
410  	 * \return Status of transition after execution
411  	 */
412  	enum pcmk__graph_status
413  	pcmk__execute_graph(pcmk__graph_t *graph)
414  	{
415  	    GList *lpc = NULL;
416  	    int log_level = LOG_DEBUG;
417  	    enum pcmk__graph_status pass_result = pcmk__graph_active;
418  	    const char *status = "In progress";
419  	
420  	    if (graph_fns == NULL) {
421  	        graph_fns = &default_fns;
422  	    }
423  	    if (graph == NULL) {
424  	        return pcmk__graph_complete;
425  	    }
426  	
427  	    graph->fired = 0;
428  	    graph->pending = 0;
429  	    graph->skipped = 0;
430  	    graph->completed = 0;
431  	    graph->incomplete = 0;
432  	
433  	    // Count completed and in-flight synapses
434  	    for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
435  	        pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
436  	
437  	        if (pcmk__is_set(synapse->flags, pcmk__synapse_confirmed)) {
438  	            graph->completed++;
439  	
440  	        } else if (!pcmk__is_set(synapse->flags, pcmk__synapse_failed)
441  	                   && pcmk__is_set(synapse->flags, pcmk__synapse_executed)) {
442  	            graph->pending++;
443  	        }
444  	    }
445  	    pcmk__trace("Executing graph %d (%d synapses already completed, %d "
446  	                "pending)",
447  	                graph->id, graph->completed, graph->pending);
448  	
449  	    // Execute any synapses that are ready
450  	    for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
451  	        pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
452  	
453  	        if ((graph->batch_limit > 0)
454  	            && (graph->pending >= graph->batch_limit)) {
455  	
456  	            pcmk__debug("Throttling graph execution: batch limit (%d) reached",
457  	                        graph->batch_limit);
458  	            break;
459  	
460  	        } else if (pcmk__is_set(synapse->flags, pcmk__synapse_failed)) {
461  	            graph->skipped++;
462  	            continue;
463  	
464  	        } else if (pcmk__any_flags_set(synapse->flags,
465  	                                       pcmk__synapse_confirmed
466  	                                       |pcmk__synapse_executed)) {
467  	            continue; // Already handled
468  	
469  	        } else if (should_fire_synapse(graph, synapse)) {
470  	            graph->fired++;
471  	            if (fire_synapse(graph, synapse) != pcmk_rc_ok) {
472  	                pcmk__err("Synapse %d failed to fire", synapse->id);
473  	                log_level = LOG_ERR;
474  	                graph->abort_priority = PCMK_SCORE_INFINITY;
475  	                graph->incomplete++;
476  	                graph->fired--;
477  	            }
478  	
479  	            if (!(pcmk__is_set(synapse->flags, pcmk__synapse_confirmed))) {
480  	                graph->pending++;
481  	            }
482  	
483  	        } else {
484  	            pcmk__trace("Synapse %d cannot fire", synapse->id);
485  	            graph->incomplete++;
486  	        }
487  	    }
488  	
489  	    if ((graph->pending == 0) && (graph->fired == 0)) {
490  	        graph->complete = true;
491  	
492  	        if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) {
493  	            log_level = LOG_WARNING;
494  	            pass_result = pcmk__graph_terminated;
495  	            status = "Terminated";
496  	
497  	        } else if (graph->skipped != 0) {
498  	            log_level = LOG_NOTICE;
499  	            pass_result = pcmk__graph_complete;
500  	            status = "Stopped";
501  	
502  	        } else {
503  	            log_level = LOG_NOTICE;
504  	            pass_result = pcmk__graph_complete;
505  	            status = "Complete";
506  	        }
507  	
508  	    } else if (graph->fired == 0) {
509  	        pass_result = pcmk__graph_pending;
510  	    }
511  	
512  	    do_crm_log(log_level,
513  	               "Transition %d (Complete=%d, Pending=%d,"
514  	               " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s",
515  	               graph->id, graph->completed, graph->pending, graph->fired,
516  	               graph->skipped, graph->incomplete, graph->source, status);
517  	
518  	    return pass_result;
519  	}
520  	
521  	
522  	/*
523  	 * Functions for unpacking transition graph XML into structs
524  	 */
525  	
526  	/*!
527  	 * \internal
528  	 * \brief Unpack a transition graph action from XML
529  	 *
530  	 * \param[in] parent      Synapse that action is part of
531  	 * \param[in] xml_action  Action XML to unparse
532  	 *
533  	 * \return Newly allocated action on success, or NULL otherwise
534  	 */
535  	static pcmk__graph_action_t *
536  	unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action)
537  	{
538  	    enum pcmk__graph_action_type action_type;
539  	    pcmk__graph_action_t *action = NULL;
540  	    const char *value = pcmk__xe_id(xml_action);
541  	
542  	    if (value == NULL) {
543  	        pcmk__err("Ignoring transition graph action without " PCMK_XA_ID
544  	                  " (bug?)");
545  	        pcmk__log_xml_trace(xml_action, "invalid");
546  	        return NULL;
547  	    }
548  	
549  	    if (pcmk__xe_is(xml_action, PCMK__XE_RSC_OP)) {
550  	        action_type = pcmk__rsc_graph_action;
551  	
552  	    } else if (pcmk__xe_is(xml_action, PCMK__XE_PSEUDO_EVENT)) {
553  	        action_type = pcmk__pseudo_graph_action;
554  	
555  	    } else if (pcmk__xe_is(xml_action, PCMK__XE_CRM_EVENT)) {
556  	        action_type = pcmk__cluster_graph_action;
557  	
558  	    } else {
559  	        pcmk__err("Ignoring transition graph action of unknown type '%s' "
560  	                  "(bug?)",
561  	                  xml_action->name);
562  	        pcmk__log_xml_trace(xml_action, "invalid");
563  	        return NULL;
564  	    }
565  	
566  	    /* @TODO Should we use pcmk__assert_alloc() here? A crash seems preferable
567  	     * to returning a graph with missing actions. Besides, there will likely be
568  	     * more allocation failures after this.
569  	     */
570  	    action = calloc(1, sizeof(pcmk__graph_action_t));
571  	    if (action == NULL) {
572  	        pcmk__crit("Cannot unpack transition graph action: %s",
573  	                   strerror(errno));
574  	        pcmk__log_xml_trace(xml_action, "lost");
575  	        return NULL;
576  	    }
577  	
578  	    pcmk__scan_min_int(value, &(action->id), -1);
579  	    action->type = pcmk__rsc_graph_action;
580  	    action->xml = pcmk__xml_copy(NULL, xml_action);
581  	    action->synapse = parent;
582  	    action->type = action_type;
583  	    action->params = xml2list(action->xml);
584  	
585  	    value = crm_meta_value(action->params, PCMK_META_TIMEOUT);
586  	    pcmk__scan_min_int(value, &(action->timeout), 0);
587  	
588  	    /* Take PCMK_META_START_DELAY into account for the timeout of the action
589  	     * timer
590  	     */
591  	    value = crm_meta_value(action->params, PCMK_META_START_DELAY);
592  	    {
593  	        int start_delay;
594  	
595  	        pcmk__scan_min_int(value, &start_delay, 0);
596  	        action->timeout += start_delay;
597  	    }
598  	
599  	    if (pcmk__guint_from_hash(action->params, CRM_META "_" PCMK_META_INTERVAL,
600  	                              0, &(action->interval_ms)) != pcmk_rc_ok) {
601  	        action->interval_ms = 0;
602  	    }
603  	
604  	    pcmk__trace("Action %d has timer set to %dms", action->id, action->timeout);
605  	
606  	    return action;
607  	}
608  	
609  	/*!
610  	 * \internal
611  	 * \brief Unpack transition graph synapse from XML
612  	 *
613  	 * \param[in,out] new_graph    Transition graph that synapse is part of
614  	 * \param[in]     xml_synapse  Synapse XML
615  	 *
616  	 * \return Newly allocated synapse on success, or NULL otherwise
617  	 */
618  	static pcmk__graph_synapse_t *
619  	unpack_synapse(pcmk__graph_t *new_graph, const xmlNode *xml_synapse)
620  	{
621  	    const char *value = NULL;
622  	    xmlNode *action_set = NULL;
623  	    pcmk__graph_synapse_t *new_synapse = NULL;
624  	
625  	    pcmk__trace("Unpacking synapse %s", pcmk__xe_id(xml_synapse));
626  	
627  	    new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t));
628  	    if (new_synapse == NULL) {
629  	        return NULL;
630  	    }
631  	
632  	    pcmk__scan_min_int(pcmk__xe_id(xml_synapse), &(new_synapse->id), 0);
633  	
634  	    value = pcmk__xe_get(xml_synapse, PCMK__XA_PRIORITY);
635  	    pcmk__scan_min_int(value, &(new_synapse->priority), 0);
636  	
637  	    CRM_CHECK(new_synapse->id >= 0,
638  	              free_graph_synapse((gpointer) new_synapse); return NULL);
639  	
640  	    new_graph->num_synapses++;
641  	
642  	    pcmk__trace("Unpacking synapse %s action sets",
643  	                pcmk__xe_get(xml_synapse, PCMK_XA_ID));
644  	
645  	    for (action_set = pcmk__xe_first_child(xml_synapse, PCMK__XE_ACTION_SET,
646  	                                           NULL, NULL);
647  	         action_set != NULL;
648  	         action_set = pcmk__xe_next(action_set, PCMK__XE_ACTION_SET)) {
649  	
650  	        for (xmlNode *action = pcmk__xe_first_child(action_set, NULL, NULL,
651  	                                                    NULL);
652  	             action != NULL; action = pcmk__xe_next(action, NULL)) {
653  	
654  	            pcmk__graph_action_t *new_action = unpack_action(new_synapse,
655  	                                                             action);
656  	
657  	            if (new_action == NULL) {
658  	                continue;
659  	            }
660  	
661  	            pcmk__trace("Adding action %d to synapse %d", new_action->id,
662  	                        new_synapse->id);
663  	            new_graph->num_actions++;
664  	            new_synapse->actions = g_list_append(new_synapse->actions,
665  	                                                 new_action);
666  	        }
667  	    }
668  	
669  	    pcmk__trace("Unpacking synapse %s inputs", pcmk__xe_id(xml_synapse));
670  	
671  	    for (xmlNode *inputs = pcmk__xe_first_child(xml_synapse, PCMK__XE_INPUTS,
672  	                                                NULL, NULL);
673  	         inputs != NULL; inputs = pcmk__xe_next(inputs, PCMK__XE_INPUTS)) {
674  	
675  	        for (xmlNode *trigger = pcmk__xe_first_child(inputs, PCMK__XE_TRIGGER,
676  	                                                     NULL, NULL);
677  	             trigger != NULL;
678  	             trigger = pcmk__xe_next(trigger, PCMK__XE_TRIGGER)) {
679  	
680  	            for (xmlNode *input = pcmk__xe_first_child(trigger, NULL, NULL,
681  	                                                       NULL);
682  	                 input != NULL; input = pcmk__xe_next(input, NULL)) {
683  	
684  	                pcmk__graph_action_t *new_input = unpack_action(new_synapse,
685  	                                                                input);
686  	
687  	                if (new_input == NULL) {
688  	                    continue;
689  	                }
690  	
691  	                pcmk__trace("Adding input %d to synapse %d", new_input->id,
692  	                            new_synapse->id);
693  	
694  	                new_synapse->inputs = g_list_append(new_synapse->inputs,
695  	                                                    new_input);
696  	            }
697  	        }
698  	    }
699  	
700  	    return new_synapse;
701  	}
702  	
703  	/*!
704  	 * \internal
705  	 * \brief Unpack transition graph XML
706  	 *
707  	 * \param[in] xml_graph  Transition graph XML to unpack
708  	 * \param[in] reference  Where the XML came from (for logging)
709  	 *
710  	 * \return Newly allocated transition graph on success, NULL otherwise
711  	 * \note The caller is responsible for freeing the return value using
712  	 *       pcmk__free_graph().
713  	 * \note The XML is expected to be structured like:
714  	         <transition_graph ...>
715  	           <synapse id="0">
716  	             <action_set>
717  	               <rsc_op id="2" ...>
718  	               ...
719  	             </action_set>
720  	             <inputs>
721  	                 <rsc_op id="1" ...
722  	                 ...
723  	             </inputs>
724  	           </synapse>
725  	           ...
726  	         </transition_graph>
727  	 */
728  	pcmk__graph_t *
729  	pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference)
730  	{
731  	    pcmk__graph_t *new_graph = NULL;
732  	
733  	    new_graph = calloc(1, sizeof(pcmk__graph_t));
734  	    if (new_graph == NULL) {
735  	        return NULL;
736  	    }
737  	
738  	    new_graph->source = strdup(pcmk__s(reference, "unknown"));
739  	    if (new_graph->source == NULL) {
740  	        pcmk__free_graph(new_graph);
741  	        return NULL;
742  	    }
743  	
744  	    new_graph->completion_action = pcmk__graph_done;
745  	
746  	    // Parse top-level attributes from PCMK__XE_TRANSITION_GRAPH
747  	    if (xml_graph != NULL) {
748  	        const char *buf = pcmk__xe_get(xml_graph, "transition_id");
749  	
750  	        CRM_CHECK(buf != NULL,
751  	                  pcmk__free_graph(new_graph); return NULL);
752  	        pcmk__scan_min_int(buf, &(new_graph->id), 1);
753  	
754  	        buf = pcmk__xe_get(xml_graph, PCMK_OPT_CLUSTER_DELAY);
755  	        CRM_CHECK(buf != NULL,
756  	                  pcmk__free_graph(new_graph); return NULL);
757  	        pcmk_parse_interval_spec(buf, &(new_graph->network_delay));
758  	
759  	        buf = pcmk__xe_get(xml_graph, PCMK_OPT_FENCING_TIMEOUT);
760  	        if (buf == NULL) {
761  	            new_graph->fencing_timeout = new_graph->network_delay;
762  	        } else {
763  	            pcmk_parse_interval_spec(buf, &(new_graph->fencing_timeout));
764  	        }
765  	
766  	        // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum
767  	        buf = pcmk__xe_get(xml_graph, PCMK_OPT_BATCH_LIMIT);
768  	        if ((buf == NULL)
769  	            || (pcmk__scan_min_int(buf, &(new_graph->batch_limit),
770  	                                   -1) != pcmk_rc_ok)) {
771  	            new_graph->batch_limit = 0;
772  	        }
773  	
774  	        buf = pcmk__xe_get(xml_graph, PCMK_OPT_MIGRATION_LIMIT);
775  	        pcmk__scan_min_int(buf, &(new_graph->migration_limit), -1);
776  	
777  	        new_graph->failed_stop_offset =
778  	            pcmk__xe_get_copy(xml_graph, PCMK__XA_FAILED_STOP_OFFSET);
779  	        new_graph->failed_start_offset =
780  	            pcmk__xe_get_copy(xml_graph, PCMK__XA_FAILED_START_OFFSET);
781  	
782  	        pcmk__xe_get_time(xml_graph, "recheck-by", &(new_graph->recheck_by));
783  	    }
784  	
785  	    // Unpack each child <synapse> element
786  	    for (const xmlNode *synapse_xml = pcmk__xe_first_child(xml_graph,
787  	                                                           PCMK__XE_SYNAPSE,
788  	                                                           NULL, NULL);
789  	         synapse_xml != NULL;
790  	         synapse_xml = pcmk__xe_next(synapse_xml, PCMK__XE_SYNAPSE)) {
791  	
792  	        pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph,
793  	                                                            synapse_xml);
794  	
795  	        if (new_synapse != NULL) {
796  	            new_graph->synapses = g_list_append(new_graph->synapses,
797  	                                                new_synapse);
798  	        }
799  	    }
800  	
801  	    pcmk__debug("Unpacked transition %d from %s: %d actions in %d synapses",
802  	                new_graph->id, new_graph->source, new_graph->num_actions,
803  	                new_graph->num_synapses);
804  	
805  	    return new_graph;
806  	}
807  	
808  	
809  	/*
810  	 * Other transition graph utilities
811  	 */
812  	
813  	/*!
814  	 * \internal
815  	 * \brief Synthesize an executor event from a graph action
816  	 *
817  	 * \param[in] resource     If not NULL, use greater call ID than in this XML
818  	 * \param[in] action       Graph action
819  	 * \param[in] status       What to use as event execution status
820  	 * \param[in] rc           What to use as event exit status
821  	 * \param[in] exit_reason  What to use as event exit reason
822  	 *
823  	 * \return Newly allocated executor event on success, or NULL otherwise
824  	 */
825  	lrmd_event_data_t *
826  	pcmk__event_from_graph_action(const xmlNode *resource,
827  	                              const pcmk__graph_action_t *action,
828  	                              int status, int rc, const char *exit_reason)
829  	{
830  	    lrmd_event_data_t *op = NULL;
831  	    GHashTableIter iter;
832  	    const char *name = NULL;
833  	    const char *value = NULL;
834  	    xmlNode *action_resource = NULL;
835  	
836  	    CRM_CHECK(action != NULL, return NULL);
837  	    CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL);
838  	
839  	    action_resource = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
840  	                                           NULL);
841  	    CRM_CHECK(action_resource != NULL,
842  	              pcmk__log_xml_warn(action->xml, "invalid"); return NULL);
843  	
844  	    op = lrmd_new_event(pcmk__xe_id(action_resource),
845  	                        pcmk__xe_get(action->xml, PCMK_XA_OPERATION),
846  	                        action->interval_ms);
847  	    lrmd__set_result(op, rc, status, exit_reason);
848  	    op->t_run = time(NULL);
849  	    op->t_rcchange = op->t_run;
850  	    op->params = pcmk__strkey_table(free, free);
851  	
852  	    g_hash_table_iter_init(&iter, action->params);
853  	    while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
854  	        pcmk__insert_dup(op->params, name, value);
855  	    }
856  	
857  	    for (xmlNode *xop = pcmk__xe_first_child(resource, NULL, NULL, NULL);
858  	         xop != NULL; xop = pcmk__xe_next(xop, NULL)) {
859  	
860  	        int tmp = 0;
861  	
862  	        pcmk__xe_get_int(xop, PCMK__XA_CALL_ID, &tmp);
863  	        pcmk__debug("Got call_id=%d for %s", tmp, pcmk__xe_id(resource));
864  	        if (tmp > op->call_id) {
865  	            op->call_id = tmp;
866  	        }
867  	    }
868  	
869  	    op->call_id++;
870  	    return op;
871  	}
872