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