1    	/*
2    	 * Copyright 2010-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   	#ifndef _GNU_SOURCE
13   	#  define _GNU_SOURCE
14   	#endif
15   	
16   	#include <sys/types.h>
17   	#include <sys/stat.h>
18   	#include <stdio.h>
19   	#include <errno.h>
20   	#include <unistd.h>
21   	#include <dirent.h>
22   	#include <fcntl.h>
23   	
24   	#include <crm/crm.h>
25   	#include <crm/common/mainloop.h>
26   	#include <crm/services.h>
27   	#include <crm/services_internal.h>
28   	#include <crm/stonith-ng.h>
29   	#include <crm/msg_xml.h>
30   	#include "services_private.h"
31   	#include "services_ocf.h"
32   	#include "services_lsb.h"
33   	
34   	#if SUPPORT_UPSTART
35   	#  include <upstart.h>
36   	#endif
37   	
38   	#if SUPPORT_SYSTEMD
39   	#  include <systemd.h>
40   	#endif
41   	
42   	#if SUPPORT_NAGIOS
43   	#  include <services_nagios.h>
44   	#endif
45   	
46   	/* TODO: Develop a rollover strategy */
47   	
48   	static int operations = 0;
49   	static GHashTable *recurring_actions = NULL;
50   	
51   	/* ops waiting to run async because of conflicting active
52   	 * pending ops */
53   	static GList *blocked_ops = NULL;
54   	
55   	/* ops currently active (in-flight) */
56   	static GList *inflight_ops = NULL;
57   	
58   	static void handle_blocked_ops(void);
59   	
60   	/*!
61   	 * \brief Find first service class that can provide a specified agent
62   	 *
63   	 * \param[in] agent  Name of agent to search for
64   	 *
65   	 * \return Service class if found, NULL otherwise
66   	 *
67   	 * \note The priority is LSB, then systemd, then upstart. It would be preferable
68   	 *       to put systemd first, but LSB merely requires a file existence check,
69   	 *       while systemd requires contacting D-Bus.
70   	 */
71   	const char *
72   	resources_find_service_class(const char *agent)
73   	{
74   	    if (services__lsb_agent_exists(agent)) {
75   	        return PCMK_RESOURCE_CLASS_LSB;
76   	    }
77   	
78   	#if SUPPORT_SYSTEMD
79   	    if (systemd_unit_exists(agent)) {
80   	        return PCMK_RESOURCE_CLASS_SYSTEMD;
81   	    }
82   	#endif
83   	
84   	#if SUPPORT_UPSTART
85   	    if (upstart_job_exists(agent)) {
86   	        return PCMK_RESOURCE_CLASS_UPSTART;
87   	    }
88   	#endif
89   	    return NULL;
90   	}
91   	
92   	static inline void
93   	init_recurring_actions(void)
94   	{
95   	    if (recurring_actions == NULL) {
96   	        recurring_actions = pcmk__strkey_table(NULL, NULL);
97   	    }
98   	}
99   	
100  	/*!
101  	 * \internal
102  	 * \brief Check whether op is in-flight systemd or upstart op
103  	 *
104  	 * \param[in] op  Operation to check
105  	 *
106  	 * \return TRUE if op is in-flight systemd or upstart op
107  	 */
108  	static inline gboolean
109  	inflight_systemd_or_upstart(const svc_action_t *op)
110  	{
111  	    return pcmk__strcase_any_of(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
112  	                           PCMK_RESOURCE_CLASS_UPSTART, NULL) &&
113  	           g_list_find(inflight_ops, op) != NULL;
114  	}
115  	
116  	/*!
117  	 * \internal
118  	 * \brief Expand "service" alias to an actual resource class
119  	 *
120  	 * \param[in] rsc       Resource name (for logging only)
121  	 * \param[in] standard  Resource class as configured
122  	 * \param[in] agent     Agent name to look for
123  	 *
124  	 * \return Newly allocated string with actual resource class
125  	 *
126  	 * \note The caller is responsible for calling free() on the result.
127  	 */
128  	static char *
129  	expand_resource_class(const char *rsc, const char *standard, const char *agent)
130  	{
131  	    char *expanded_class = NULL;
132  	
133  	    if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
134  	        const char *found_class = resources_find_service_class(agent);
135  	
136  	        if (found_class) {
137  	            crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
138  	            expanded_class = strdup(found_class);
139  	        } else {
140  	            crm_info("Assuming resource class lsb for agent %s for %s",
141  	                     agent, rsc);
142  	            expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
143  	        }
144  	    } else {
145  	        expanded_class = strdup(standard);
146  	    }
147  	    CRM_ASSERT(expanded_class);
148  	    return expanded_class;
149  	}
150  	
151  	/*!
152  	 * \internal
153  	 * \brief Create a simple svc_action_t instance
154  	 *
155  	 * \return Newly allocated instance (or NULL if not enough memory)
156  	 */
157  	static svc_action_t *
158  	new_action(void)
159  	{
160  	    svc_action_t *op = calloc(1, sizeof(svc_action_t));
161  	
162  	    if (op == NULL) {
163  	        return NULL;
164  	    }
165  	
166  	    op->opaque = calloc(1, sizeof(svc_action_private_t));
167  	    if (op->opaque == NULL) {
168  	        free(op);
169  	        return NULL;
170  	    }
171  	
172  	    // Initialize result
173  	    services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN, NULL);
174  	    return op;
175  	}
176  	
177  	static bool
178  	required_argument_missing(uint32_t ra_caps, const char *name,
179  	                          const char *standard, const char *provider,
180  	                          const char *agent, const char *action)
181  	{
182  	    if (pcmk__str_empty(name)) {
183  	        crm_info("Cannot create operation without resource name (bug?)");
184  	        return true;
185  	    }
186  	
187  	    if (pcmk__str_empty(standard)) {
188  	        crm_info("Cannot create operation for %s without resource class (bug?)",
189  	                 name);
190  	        return true;
191  	    }
192  	
193  	    if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)
194  	        && pcmk__str_empty(provider)) {
195  	        crm_info("Cannot create operation for %s resource %s "
196  	                 "without provider (bug?)", standard, name);
197  	        return true;
198  	    }
199  	
200  	    if (pcmk__str_empty(agent)) {
201  	        crm_info("Cannot create operation for %s without agent name (bug?)",
202  	                 name);
203  	        return true;
204  	    }
205  	
206  	    if (pcmk__str_empty(action)) {
207  	        crm_info("Cannot create operation for %s without action name (bug?)",
208  	                 name);
209  	        return true;
210  	    }
211  	    return false;
212  	}
213  	
214  	// \return Standard Pacemaker return code (pcmk_rc_ok or ENOMEM)
215  	static int
216  	copy_action_arguments(svc_action_t *op, uint32_t ra_caps, const char *name,
217  	                      const char *standard, const char *provider,
218  	                      const char *agent, const char *action)
219  	{
220  	    op->rsc = strdup(name);
221  	    if (op->rsc == NULL) {
222  	        return ENOMEM;
223  	    }
224  	
225  	    op->agent = strdup(agent);
226  	    if (op->agent == NULL) {
227  	        return ENOMEM;
228  	    }
229  	
230  	    op->standard = expand_resource_class(name, standard, agent);
231  	    if (op->standard == NULL) {
232  	        return ENOMEM;
233  	    }
234  	
235  	    if (pcmk_is_set(ra_caps, pcmk_ra_cap_status)
236  	        && pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
237  	        action = PCMK_ACTION_STATUS;
238  	    }
239  	    op->action = strdup(action);
240  	    if (op->action == NULL) {
241  	        return ENOMEM;
242  	    }
243  	
244  	    if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)) {
245  	        op->provider = strdup(provider);
246  	        if (op->provider == NULL) {
247  	            return ENOMEM;
248  	        }
249  	    }
250  	    return pcmk_rc_ok;
251  	}
252  	
253  	svc_action_t *
254  	services__create_resource_action(const char *name, const char *standard,
255  	                        const char *provider, const char *agent,
256  	                        const char *action, guint interval_ms, int timeout,
257  	                        GHashTable *params, enum svc_action_flags flags)
258  	{
259  	    svc_action_t *op = NULL;
260  	    uint32_t ra_caps = pcmk_get_ra_caps(standard);
261  	    int rc = pcmk_rc_ok;
262  	
263  	    op = new_action();
264  	    if (op == NULL) {
265  	        crm_crit("Cannot prepare action: %s", strerror(ENOMEM));
266  	        if (params != NULL) {
267  	            g_hash_table_destroy(params);
268  	        }
269  	        return NULL;
270  	    }
271  	
272  	    op->interval_ms = interval_ms;
273  	    op->timeout = timeout;
274  	    op->flags = flags;
275  	    op->sequence = ++operations;
276  	
277  	    // Take ownership of params
278  	    if (pcmk_is_set(ra_caps, pcmk_ra_cap_params)) {
279  	        op->params = params;
280  	    } else if (params != NULL) {
281  	        g_hash_table_destroy(params);
282  	        params = NULL;
283  	    }
284  	
285  	    if (required_argument_missing(ra_caps, name, standard, provider, agent,
286  	                                  action)) {
287  	        services__set_result(op, services__generic_error(op),
288  	                             PCMK_EXEC_ERROR_FATAL,
289  	                             "Required agent or action information missing");
290  	        return op;
291  	    }
292  	
293  	    op->id = pcmk__op_key(name, action, interval_ms);
294  	
295  	    if (copy_action_arguments(op, ra_caps, name, standard, provider, agent,
296  	                              action) != pcmk_rc_ok) {
297  	        crm_crit("Cannot prepare %s action for %s: %s",
298  	                 action, name, strerror(ENOMEM));
299  	        services__handle_exec_error(op, ENOMEM);
300  	        return op;
301  	    }
302  	
303  	    if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
304  	        rc = services__ocf_prepare(op);
305  	
306  	    } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
307  	        rc = services__lsb_prepare(op);
308  	
309  	#if SUPPORT_SYSTEMD
310  	    } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
311  	        rc = services__systemd_prepare(op);
312  	#endif
313  	#if SUPPORT_UPSTART
314  	    } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
315  	        rc = services__upstart_prepare(op);
316  	#endif
317  	#if SUPPORT_NAGIOS
318  	    } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
319  	        rc = services__nagios_prepare(op);
320  	#endif
321  	    } else {
322  	        crm_info("Unknown resource standard: %s", op->standard);
323  	        rc = ENOENT;
324  	    }
325  	
326  	    if (rc != pcmk_rc_ok) {
327  	        crm_info("Cannot prepare %s operation for %s: %s",
328  	                 action, name, strerror(rc));
329  	        services__handle_exec_error(op, rc);
330  	    }
331  	    return op;
332  	}
333  	
334  	svc_action_t *
335  	resources_action_create(const char *name, const char *standard,
336  	                        const char *provider, const char *agent,
337  	                        const char *action, guint interval_ms, int timeout,
338  	                        GHashTable *params, enum svc_action_flags flags)
339  	{
340  	    svc_action_t *op = services__create_resource_action(name, standard,
341  	                            provider, agent, action, interval_ms, timeout,
342  	                            params, flags);
343  	    if (op == NULL || op->rc != 0) {
344  	        services_action_free(op);
345  	        return NULL;
346  	    } else {
347  	        // Preserve public API backward compatibility
348  	        op->rc = PCMK_OCF_OK;
349  	        op->status = PCMK_EXEC_DONE;
350  	
351  	        return op;
352  	    }
353  	}
354  	
355  	svc_action_t *
356  	services_action_create_generic(const char *exec, const char *args[])
357  	{
358  	    svc_action_t *op = new_action();
359  	
360  	    CRM_ASSERT(op != NULL);
361  	
362  	    op->opaque->exec = strdup(exec);
363  	    op->opaque->args[0] = strdup(exec);
364  	    if ((op->opaque->exec == NULL) || (op->opaque->args[0] == NULL)) {
365  	        crm_crit("Cannot prepare action for '%s': %s", exec, strerror(ENOMEM));
366  	        services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
367  	                             strerror(ENOMEM));
368  	        return op;
369  	    }
370  	
371  	    if (args == NULL) {
372  	        return op;
373  	    }
374  	
375  	    for (int cur_arg = 1; args[cur_arg - 1] != NULL; cur_arg++) {
376  	
377  	        if (cur_arg == PCMK__NELEM(op->opaque->args)) {
378  	            crm_info("Cannot prepare action for '%s': Too many arguments",
379  	                     exec);
380  	            services__set_result(op, PCMK_OCF_UNKNOWN_ERROR,
381  	                                 PCMK_EXEC_ERROR_HARD, "Too many arguments");
382  	            break;
383  	        }
384  	
385  	        op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
386  	        if (op->opaque->args[cur_arg] == NULL) {
387  	            crm_crit("Cannot prepare action for '%s': %s",
388  	                     exec, strerror(ENOMEM));
389  	            services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
390  	                                 strerror(ENOMEM));
391  	            break;
392  	        }
393  	    }
394  	
395  	    return op;
396  	}
397  	
398  	/*!
399  	 * \brief Create an alert agent action
400  	 *
401  	 * \param[in] id        Alert ID
402  	 * \param[in] exec      Path to alert agent executable
403  	 * \param[in] timeout   Action timeout
404  	 * \param[in] params    Parameters to use with action
405  	 * \param[in] sequence  Action sequence number
406  	 * \param[in] cb_data   Data to pass to callback function
407  	 *
408  	 * \return New action on success, NULL on error
409  	 * \note It is the caller's responsibility to free cb_data.
410  	 *       The caller should not free params explicitly.
411  	 */
412  	svc_action_t *
413  	services_alert_create(const char *id, const char *exec, int timeout,
414  	                      GHashTable *params, int sequence, void *cb_data)
415  	{
416  	    svc_action_t *action = services_action_create_generic(exec, NULL);
417  	
418  	    action->id = strdup(id);
419  	    action->standard = strdup(PCMK_RESOURCE_CLASS_ALERT);
420  	    CRM_ASSERT((action->id != NULL) && (action->standard != NULL));
421  	
422  	    action->timeout = timeout;
423  	    action->params = params;
424  	    action->sequence = sequence;
425  	    action->cb_data = cb_data;
426  	    return action;
427  	}
428  	
429  	/*!
430  	 * \brief Set the user and group that an action will execute as
431  	 *
432  	 * \param[in,out] op      Action to modify
433  	 * \param[in]     user    Name of user to execute action as
434  	 * \param[in]     group   Name of group to execute action as
435  	 *
436  	 * \return pcmk_ok on success, -errno otherwise
437  	 *
438  	 * \note This will have no effect unless the process executing the action runs
439  	 *       as root, and the action is not a systemd or upstart action.
440  	 *       We could implement this for systemd by adding User= and Group= to
441  	 *       [Service] in the override file, but that seems more likely to cause
442  	 *       problems than be useful.
443  	 */
444  	int
445  	services_action_user(svc_action_t *op, const char *user)
446  	{
447  	    CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
448  	    return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
449  	}
450  	
451  	/*!
452  	 * \brief Execute an alert agent action
453  	 *
454  	 * \param[in,out] action  Action to execute
455  	 * \param[in]     cb      Function to call when action completes
456  	 *
457  	 * \return TRUE if the library will free action, FALSE otherwise
458  	 *
459  	 * \note If this function returns FALSE, it is the caller's responsibility to
460  	 *       free the action with services_action_free(). However, unless someone
461  	 *       intentionally creates a recurring alert action, this will never return
462  	 *       FALSE.
463  	 */
464  	gboolean
465  	services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
466  	{
467  	    action->synchronous = false;
468  	    action->opaque->callback = cb;
469  	    return services__execute_file(action) == pcmk_rc_ok;
470  	}
471  	
472  	#if HAVE_DBUS
473  	/*!
474  	 * \internal
475  	 * \brief Update operation's pending DBus call, unreferencing old one if needed
476  	 *
477  	 * \param[in,out] op       Operation to modify
478  	 * \param[in]     pending  Pending call to set
479  	 */
480  	void
481  	services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
482  	{
483  	    if (op->opaque->pending && (op->opaque->pending != pending)) {
484  	        if (pending) {
485  	            crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
486  	        } else {
487  	            crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
488  	        }
489  	        dbus_pending_call_unref(op->opaque->pending);
490  	    }
491  	    op->opaque->pending = pending;
492  	    if (pending) {
493  	        crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
494  	    } else {
495  	        crm_trace("Cleared pending %s DBus call", op->id);
496  	    }
497  	}
498  	#endif
499  	
500  	void
501  	services_action_cleanup(svc_action_t * op)
502  	{
503  	    if ((op == NULL) || (op->opaque == NULL)) {
504  	        return;
505  	    }
506  	
507  	#if HAVE_DBUS
508  	    if(op->opaque->timerid != 0) {
509  	        crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
510  	        g_source_remove(op->opaque->timerid);
511  	        op->opaque->timerid = 0;
512  	    }
513  	
514  	    if(op->opaque->pending) {
515  	        if (dbus_pending_call_get_completed(op->opaque->pending)) {
516  	            // This should never be the case
517  	            crm_warn("Result of %s op %s was unhandled",
518  	                     op->standard, op->id);
519  	        } else {
520  	            crm_debug("Will ignore any result of canceled %s op %s",
521  	                      op->standard, op->id);
522  	        }
523  	        dbus_pending_call_cancel(op->opaque->pending);
524  	        services_set_op_pending(op, NULL);
525  	    }
526  	#endif
527  	
528  	    if (op->opaque->stderr_gsource) {
529  	        mainloop_del_fd(op->opaque->stderr_gsource);
530  	        op->opaque->stderr_gsource = NULL;
531  	    }
532  	
533  	    if (op->opaque->stdout_gsource) {
534  	        mainloop_del_fd(op->opaque->stdout_gsource);
535  	        op->opaque->stdout_gsource = NULL;
536  	    }
537  	}
538  	
539  	/*!
540  	 * \internal
541  	 * \brief Map an actual resource action result to a standard OCF result
542  	 *
543  	 * \param[in] standard     Agent standard (must not be "service")
544  	 * \param[in] action       Action that result is for
545  	 * \param[in] exit_status  Actual agent exit status
546  	 *
547  	 * \return Standard OCF result
548  	 */
549  	enum ocf_exitcode
550  	services_result2ocf(const char *standard, const char *action, int exit_status)
551  	{
552  	    if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
553  	        return services__ocf2ocf(exit_status);
554  	
555  	#if SUPPORT_SYSTEMD
556  	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD,
557  	                            pcmk__str_casei)) {
558  	        return services__systemd2ocf(exit_status);
559  	#endif
560  	
561  	#if SUPPORT_UPSTART
562  	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART,
563  	                            pcmk__str_casei)) {
564  	        return services__upstart2ocf(exit_status);
565  	#endif
566  	
567  	#if SUPPORT_NAGIOS
568  	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS,
569  	                            pcmk__str_casei)) {
570  	        return services__nagios2ocf(exit_status);
571  	#endif
572  	
573  	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB,
574  	                            pcmk__str_casei)) {
575  	        return services__lsb2ocf(action, exit_status);
576  	
577  	    } else {
578  	        crm_warn("Treating result from unknown standard '%s' as OCF",
579  	                 ((standard == NULL)? "unspecified" : standard));
580  	        return services__ocf2ocf(exit_status);
581  	    }
582  	}
583  	
584  	void
585  	services_action_free(svc_action_t * op)
586  	{
587  	    unsigned int i;
588  	
589  	    if (op == NULL) {
590  	        return;
591  	    }
592  	
593  	    /* The operation should be removed from all tracking lists by this point.
594  	     * If it's not, we have a bug somewhere, so bail. That may lead to a
595  	     * memory leak, but it's better than a use-after-free segmentation fault.
596  	     */
597  	    CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
598  	    CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
599  	    CRM_CHECK((recurring_actions == NULL)
600  	              || (g_hash_table_lookup(recurring_actions, op->id) == NULL),
601  	              return);
602  	
603  	    services_action_cleanup(op);
604  	
605  	    if (op->opaque->repeat_timer) {
606  	        g_source_remove(op->opaque->repeat_timer);
607  	        op->opaque->repeat_timer = 0;
608  	    }
609  	
610  	    free(op->id);
611  	    free(op->opaque->exec);
612  	
613  	    for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
614  	        free(op->opaque->args[i]);
615  	    }
616  	
617  	    free(op->opaque->exit_reason);
618  	    free(op->opaque);
619  	    free(op->rsc);
620  	    free(op->action);
621  	
622  	    free(op->standard);
623  	    free(op->agent);
624  	    free(op->provider);
625  	
626  	    free(op->stdout_data);
627  	    free(op->stderr_data);
628  	
629  	    if (op->params) {
630  	        g_hash_table_destroy(op->params);
631  	        op->params = NULL;
632  	    }
633  	
634  	    free(op);
635  	}
636  	
637  	gboolean
638  	cancel_recurring_action(svc_action_t * op)
639  	{
640  	    crm_info("Cancelling %s operation %s", op->standard, op->id);
641  	
642  	    if (recurring_actions) {
643  	        g_hash_table_remove(recurring_actions, op->id);
644  	    }
645  	
646  	    if (op->opaque->repeat_timer) {
647  	        g_source_remove(op->opaque->repeat_timer);
648  	        op->opaque->repeat_timer = 0;
649  	    }
650  	
651  	    return TRUE;
652  	}
653  	
654  	/*!
655  	 * \brief Cancel a recurring action
656  	 *
657  	 * \param[in] name         Name of resource that operation is for
658  	 * \param[in] action       Name of operation to cancel
659  	 * \param[in] interval_ms  Interval of operation to cancel
660  	 *
661  	 * \return TRUE if action was successfully cancelled, FALSE otherwise
662  	 */
663  	gboolean
664  	services_action_cancel(const char *name, const char *action, guint interval_ms)
665  	{
666  	    gboolean cancelled = FALSE;
667  	    char *id = pcmk__op_key(name, action, interval_ms);
668  	    svc_action_t *op = NULL;
669  	
670  	    /* We can only cancel a recurring action */
671  	    init_recurring_actions();
672  	    op = g_hash_table_lookup(recurring_actions, id);
673  	    if (op == NULL) {
674  	        goto done;
675  	    }
676  	
677  	    // Tell services__finalize_async_op() not to reschedule the operation
678  	    op->cancel = TRUE;
679  	
680  	    /* Stop tracking it as a recurring operation, and stop its repeat timer */
681  	    cancel_recurring_action(op);
682  	
683  	    /* If the op has a PID, it's an in-flight child process, so kill it.
684  	     *
685  	     * Whether the kill succeeds or fails, the main loop will send the op to
686  	     * async_action_complete() (and thus services__finalize_async_op()) when the
687  	     * process goes away.
688  	     */
689  	    if (op->pid != 0) {
690  	        crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
691  	                 id, op->pid);
692  	        cancelled = mainloop_child_kill(op->pid);
693  	        if (cancelled == FALSE) {
694  	            crm_err("Termination of %s[%d] failed", id, op->pid);
695  	        }
696  	        goto done;
697  	    }
698  	
699  	#if HAVE_DBUS
700  	    // In-flight systemd and upstart ops don't have a pid
701  	    if (inflight_systemd_or_upstart(op)) {
702  	        inflight_ops = g_list_remove(inflight_ops, op);
703  	
704  	        /* This will cause any result that comes in later to be discarded, so we
705  	         * don't call the callback and free the operation twice.
706  	         */
707  	        services_action_cleanup(op);
708  	    }
709  	#endif
710  	
711  	    /* The rest of this is essentially equivalent to
712  	     * services__finalize_async_op(), minus the handle_blocked_ops() call.
713  	     */
714  	
715  	    // Report operation as cancelled
716  	    services__set_cancelled(op);
717  	    if (op->opaque->callback) {
718  	        op->opaque->callback(op);
719  	    }
720  	
721  	    blocked_ops = g_list_remove(blocked_ops, op);
722  	    services_action_free(op);
723  	    cancelled = TRUE;
724  	    // @TODO Initiate handle_blocked_ops() asynchronously
725  	
726  	done:
727  	    free(id);
728  	    return cancelled;
729  	}
730  	
731  	gboolean
732  	services_action_kick(const char *name, const char *action, guint interval_ms)
733  	{
734  	    svc_action_t * op = NULL;
735  	    char *id = pcmk__op_key(name, action, interval_ms);
736  	
737  	    init_recurring_actions();
738  	    op = g_hash_table_lookup(recurring_actions, id);
739  	    free(id);
740  	
741  	    if (op == NULL) {
742  	        return FALSE;
743  	    }
744  	
745  	
746  	    if (op->pid || inflight_systemd_or_upstart(op)) {
747  	        return TRUE;
748  	    } else {
749  	        if (op->opaque->repeat_timer) {
750  	            g_source_remove(op->opaque->repeat_timer);
751  	            op->opaque->repeat_timer = 0;
752  	        }
753  	        recurring_action_timer(op);
754  	        return TRUE;
755  	    }
756  	
757  	}
758  	
759  	/*!
760  	 * \internal
761  	 * \brief Add a new recurring operation, checking for duplicates
762  	 *
763  	 * \param[in,out] op  Operation to add
764  	 *
765  	 * \return TRUE if duplicate found (and reschedule), FALSE otherwise
766  	 */
767  	static gboolean
768  	handle_duplicate_recurring(svc_action_t *op)
769  	{
770  	    svc_action_t * dup = NULL;
771  	
772  	    /* check for duplicates */
773  	    dup = g_hash_table_lookup(recurring_actions, op->id);
774  	
775  	    if (dup && (dup != op)) {
776  	        /* update user data */
777  	        if (op->opaque->callback) {
778  	            dup->opaque->callback = op->opaque->callback;
779  	            dup->cb_data = op->cb_data;
780  	            op->cb_data = NULL;
781  	        }
782  	        /* immediately execute the next interval */
783  	        if (dup->pid != 0) {
784  	            if (op->opaque->repeat_timer) {
785  	                g_source_remove(op->opaque->repeat_timer);
786  	                op->opaque->repeat_timer = 0;
787  	            }
788  	            recurring_action_timer(dup);
789  	        }
790  	        /* free the duplicate */
791  	        services_action_free(op);
792  	        return TRUE;
793  	    }
794  	
795  	    return FALSE;
796  	}
797  	
798  	/*!
799  	 * \internal
800  	 * \brief Execute an action appropriately according to its standard
801  	 *
802  	 * \param[in,out] op  Action to execute
803  	 *
804  	 * \return Standard Pacemaker return code
805  	 * \retval EBUSY          Recurring operation could not be initiated
806  	 * \retval pcmk_rc_error  Synchronous action failed
807  	 * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
808  	 *                        should not be freed (because it's pending or because
809  	 *                        it failed to execute and was already freed)
810  	 *
811  	 * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
812  	 *       caller is responsible for freeing the action.
813  	 */
814  	static int
815  	execute_action(svc_action_t *op)
816  	{
817  	#if SUPPORT_UPSTART
818  	    if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART,
819  	                     pcmk__str_casei)) {
820  	        return services__execute_upstart(op);
821  	    }
822  	#endif
823  	
824  	#if SUPPORT_SYSTEMD
825  	    if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
826  	                     pcmk__str_casei)) {
827  	        return services__execute_systemd(op);
828  	    }
829  	#endif
830  	
831  	    return services__execute_file(op);
832  	}
833  	
834  	void
835  	services_add_inflight_op(svc_action_t * op)
836  	{
837  	    if (op == NULL) {
838  	        return;
839  	    }
840  	
841  	    CRM_ASSERT(op->synchronous == FALSE);
842  	
843  	    /* keep track of ops that are in-flight to avoid collisions in the same namespace */
844  	    if (op->rsc) {
845  	        inflight_ops = g_list_append(inflight_ops, op);
846  	    }
847  	}
848  	
849  	/*!
850  	 * \internal
851  	 * \brief Stop tracking an operation that completed
852  	 *
853  	 * \param[in] op  Operation to stop tracking
854  	 */
855  	void
856  	services_untrack_op(const svc_action_t *op)
857  	{
858  	    /* Op is no longer in-flight or blocked */
859  	    inflight_ops = g_list_remove(inflight_ops, op);
860  	    blocked_ops = g_list_remove(blocked_ops, op);
861  	
862  	    /* Op is no longer blocking other ops, so check if any need to run */
863  	    handle_blocked_ops();
864  	}
865  	
866  	gboolean
867  	services_action_async_fork_notify(svc_action_t * op,
868  	                                  void (*action_callback) (svc_action_t *),
869  	                                  void (*action_fork_callback) (svc_action_t *))
870  	{
871  	    CRM_CHECK(op != NULL, return TRUE);
872  	
873  	    op->synchronous = false;
874  	    if (action_callback != NULL) {
875  	        op->opaque->callback = action_callback;
876  	    }
877  	    if (action_fork_callback != NULL) {
878  	        op->opaque->fork_callback = action_fork_callback;
879  	    }
880  	
881  	    if (op->interval_ms > 0) {
882  	        init_recurring_actions();
883  	        if (handle_duplicate_recurring(op)) {
884  	            /* entry rescheduled, dup freed */
885  	            /* exit early */
886  	            return TRUE;
887  	        }
888  	        g_hash_table_replace(recurring_actions, op->id, op);
889  	    }
890  	
891  	    if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
892  	        && op->rsc && is_op_blocked(op->rsc)) {
893  	        blocked_ops = g_list_append(blocked_ops, op);
894  	        return TRUE;
895  	    }
896  	
897  	    return execute_action(op) == pcmk_rc_ok;
898  	}
899  	
900  	gboolean
901  	services_action_async(svc_action_t * op,
902  	                      void (*action_callback) (svc_action_t *))
903  	{
904  	    return services_action_async_fork_notify(op, action_callback, NULL);
905  	}
906  	
907  	static gboolean processing_blocked_ops = FALSE;
908  	
909  	gboolean
910  	is_op_blocked(const char *rsc)
911  	{
912  	    GList *gIter = NULL;
913  	    svc_action_t *op = NULL;
914  	
915  	    for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
916  	        op = gIter->data;
917  	        if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
918  	            return TRUE;
919  	        }
920  	    }
921  	
922  	    return FALSE;
923  	}
924  	
925  	static void
926  	handle_blocked_ops(void)
927  	{
928  	    GList *executed_ops = NULL;
929  	    GList *gIter = NULL;
930  	    svc_action_t *op = NULL;
931  	
932  	    if (processing_blocked_ops) {
933  	        /* avoid nested calling of this function */
934  	        return;
935  	    }
936  	
937  	    processing_blocked_ops = TRUE;
938  	
939  	    /* n^2 operation here, but blocked ops are incredibly rare. this list
940  	     * will be empty 99% of the time. */
941  	    for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
942  	        op = gIter->data;
943  	        if (is_op_blocked(op->rsc)) {
944  	            continue;
945  	        }
946  	        executed_ops = g_list_append(executed_ops, op);
947  	        if (execute_action(op) != pcmk_rc_ok) {
948  	            /* this can cause this function to be called recursively
949  	             * which is why we have processing_blocked_ops static variable */
950  	            services__finalize_async_op(op);
951  	        }
952  	    }
953  	
954  	    for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
955  	        op = gIter->data;
956  	        blocked_ops = g_list_remove(blocked_ops, op);
957  	    }
958  	    g_list_free(executed_ops);
959  	
960  	    processing_blocked_ops = FALSE;
961  	}
962  	
963  	/*!
964  	 * \internal
965  	 * \brief Execute a meta-data action appropriately to standard
966  	 *
967  	 * \param[in,out] op  Meta-data action to execute
968  	 *
969  	 * \return Standard Pacemaker return code
970  	 */
971  	static int
972  	execute_metadata_action(svc_action_t *op)
973  	{
974  	    const char *class = op->standard;
975  	
(1) Event path: Condition "op->agent == NULL", taking false branch.
976  	    if (op->agent == NULL) {
977  	        crm_info("Meta-data requested without specifying agent");
978  	        services__set_result(op, services__generic_error(op),
979  	                             PCMK_EXEC_ERROR_FATAL, "Agent not specified");
980  	        return EINVAL;
981  	    }
982  	
(2) Event path: Condition "class == NULL", taking false branch.
983  	    if (class == NULL) {
984  	        crm_info("Meta-data requested for agent %s without specifying class",
985  	                op->agent);
986  	        services__set_result(op, services__generic_error(op),
987  	                             PCMK_EXEC_ERROR_FATAL,
988  	                             "Agent standard not specified");
989  	        return EINVAL;
990  	    }
991  	
(3) Event path: Condition "!strcmp(class, "service")", taking true branch.
992  	    if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
993  	        class = resources_find_service_class(op->agent);
994  	    }
(4) Event path: Condition "class == NULL", taking false branch.
995  	    if (class == NULL) {
996  	        crm_info("Meta-data requested for %s, but could not determine class",
997  	                 op->agent);
998  	        services__set_result(op, services__generic_error(op),
999  	                             PCMK_EXEC_ERROR_HARD,
1000 	                             "Agent standard could not be determined");
1001 	        return EINVAL;
1002 	    }
1003 	
(5) Event path: Condition "pcmk__str_eq(class, "lsb", pcmk__str_casei)", taking false branch.
1004 	    if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1005 	        return pcmk_legacy2rc(services__get_lsb_metadata(op->agent,
1006 	                                                         &op->stdout_data));
1007 	    }
1008 	
1009 	#if SUPPORT_NAGIOS
(6) Event path: Condition "pcmk__str_eq(class, "nagios", pcmk__str_casei)", taking true branch.
1010 	    if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
(7) Event string_null_source: Function "services__get_nagios_metadata" does not terminate string "op->stdout_data". [details]
1011 	        return pcmk_legacy2rc(services__get_nagios_metadata(op->agent,
1012 	                                                            &op->stdout_data));
1013 	    }
1014 	#endif
1015 	
1016 	    return execute_action(op);
1017 	}
1018 	
1019 	gboolean
1020 	services_action_sync(svc_action_t * op)
1021 	{
1022 	    gboolean rc = TRUE;
1023 	
(1) Event path: Condition "op == NULL", taking false branch.
1024 	    if (op == NULL) {
1025 	        crm_trace("No operation to execute");
1026 	        return FALSE;
1027 	    }
1028 	
1029 	    op->synchronous = true;
1030 	
(2) Event path: Condition "pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)", taking true branch.
1031 	    if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1032 	        /* Synchronous meta-data operations are handled specially. Since most
1033 	         * resource classes don't provide any meta-data, it has to be
1034 	         * synthesized from available information about the agent.
1035 	         *
1036 	         * services_action_async() doesn't treat meta-data actions specially, so
1037 	         * it will result in an error for classes that don't support the action.
1038 	         */
(3) Event string_null_source: Function "execute_metadata_action" does not terminate string "op->stdout_data". [details]
(4) Event path: Condition "execute_metadata_action(op) == pcmk_rc_ok", taking true branch.
1039 	        rc = (execute_metadata_action(op) == pcmk_rc_ok);
(5) Event path: Falling through to end of if statement.
1040 	    } else {
1041 	        rc = (execute_action(op) == pcmk_rc_ok);
1042 	    }
(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 false branch.
(9) Event path: Breaking from switch.
1043 	    crm_trace(" > " PCMK__OP_FMT ": %s = %d",
1044 	              op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
(10) Event path: Condition "op->stdout_data", taking true branch.
1045 	    if (op->stdout_data) {
(11) Event path: Switch case default.
(12) Event path: Condition "trace_cs == NULL", taking true branch.
(13) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(14) Event path: Breaking from switch.
1046 	        crm_trace(" >  stdout: %s", op->stdout_data);
1047 	    }
(15) Event path: Condition "op->stderr_data", taking true branch.
1048 	    if (op->stderr_data) {
(16) Event path: Switch case default.
(17) Event path: Condition "trace_cs == NULL", taking true branch.
(18) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(19) Event path: Breaking from switch.
1049 	        crm_trace(" >  stderr: %s", op->stderr_data);
1050 	    }
1051 	    return rc;
1052 	}
1053 	
1054 	GList *
1055 	get_directory_list(const char *root, gboolean files, gboolean executable)
1056 	{
1057 	    return services_os_get_directory_list(root, files, executable);
1058 	}
1059 	
1060 	GList *
1061 	resources_list_standards(void)
1062 	{
1063 	    GList *standards = NULL;
1064 	
1065 	    standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
1066 	    standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
1067 	    standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
1068 	
1069 	#if SUPPORT_SYSTEMD
1070 	    {
1071 	        GList *agents = systemd_unit_listall();
1072 	
1073 	        if (agents != NULL) {
1074 	            standards = g_list_append(standards,
1075 	                                      strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
1076 	            g_list_free_full(agents, free);
1077 	        }
1078 	    }
1079 	#endif
1080 	
1081 	#if SUPPORT_UPSTART
1082 	    {
1083 	        GList *agents = upstart_job_listall();
1084 	
1085 	        if (agents != NULL) {
1086 	            standards = g_list_append(standards,
1087 	                                      strdup(PCMK_RESOURCE_CLASS_UPSTART));
1088 	            g_list_free_full(agents, free);
1089 	        }
1090 	    }
1091 	#endif
1092 	
1093 	#if SUPPORT_NAGIOS
1094 	    {
1095 	        GList *agents = services__list_nagios_agents();
1096 	
1097 	        if (agents != NULL) {
1098 	            standards = g_list_append(standards,
1099 	                                      strdup(PCMK_RESOURCE_CLASS_NAGIOS));
1100 	            g_list_free_full(agents, free);
1101 	        }
1102 	    }
1103 	#endif
1104 	
1105 	    return standards;
1106 	}
1107 	
1108 	GList *
1109 	resources_list_providers(const char *standard)
1110 	{
1111 	    if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
1112 	        return resources_os_list_ocf_providers();
1113 	    }
1114 	
1115 	    return NULL;
1116 	}
1117 	
1118 	GList *
1119 	resources_list_agents(const char *standard, const char *provider)
1120 	{
1121 	    if ((standard == NULL)
1122 	        || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
1123 	
1124 	        GList *tmp1;
1125 	        GList *tmp2;
1126 	        GList *result = services__list_lsb_agents();
1127 	
1128 	        if (standard == NULL) {
1129 	            tmp1 = result;
1130 	            tmp2 = resources_os_list_ocf_agents(NULL);
1131 	            if (tmp2) {
1132 	                result = g_list_concat(tmp1, tmp2);
1133 	            }
1134 	        }
1135 	#if SUPPORT_SYSTEMD
1136 	        tmp1 = result;
1137 	        tmp2 = systemd_unit_listall();
1138 	        if (tmp2) {
1139 	            result = g_list_concat(tmp1, tmp2);
1140 	        }
1141 	#endif
1142 	
1143 	#if SUPPORT_UPSTART
1144 	        tmp1 = result;
1145 	        tmp2 = upstart_job_listall();
1146 	        if (tmp2) {
1147 	            result = g_list_concat(tmp1, tmp2);
1148 	        }
1149 	#endif
1150 	
1151 	        return result;
1152 	
1153 	    } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
1154 	        return resources_os_list_ocf_agents(provider);
1155 	    } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
1156 	        return services__list_lsb_agents();
1157 	#if SUPPORT_SYSTEMD
1158 	    } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
1159 	        return systemd_unit_listall();
1160 	#endif
1161 	#if SUPPORT_UPSTART
1162 	    } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
1163 	        return upstart_job_listall();
1164 	#endif
1165 	#if SUPPORT_NAGIOS
1166 	    } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
1167 	        return services__list_nagios_agents();
1168 	#endif
1169 	    }
1170 	
1171 	    return NULL;
1172 	}
1173 	
1174 	gboolean
1175 	resources_agent_exists(const char *standard, const char *provider, const char *agent)
1176 	{
1177 	    GList *standards = NULL;
1178 	    GList *providers = NULL;
1179 	    GList *iter = NULL;
1180 	    gboolean rc = FALSE;
1181 	    gboolean has_providers = FALSE;
1182 	
1183 	    standards = resources_list_standards();
1184 	    for (iter = standards; iter != NULL; iter = iter->next) {
1185 	        if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
1186 	            rc = TRUE;
1187 	            break;
1188 	        }
1189 	    }
1190 	
1191 	    if (rc == FALSE) {
1192 	        goto done;
1193 	    }
1194 	
1195 	    rc = FALSE;
1196 	
1197 	    has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
1198 	    if (has_providers == TRUE && provider != NULL) {
1199 	        providers = resources_list_providers(standard);
1200 	        for (iter = providers; iter != NULL; iter = iter->next) {
1201 	            if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
1202 	                rc = TRUE;
1203 	                break;
1204 	            }
1205 	        }
1206 	    } else if (has_providers == FALSE && provider == NULL) {
1207 	        rc = TRUE;
1208 	    }
1209 	
1210 	    if (rc == FALSE) {
1211 	        goto done;
1212 	    }
1213 	
1214 	    if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
1215 	        if (services__lsb_agent_exists(agent)) {
1216 	            rc = TRUE;
1217 	#if SUPPORT_SYSTEMD
1218 	        } else if (systemd_unit_exists(agent)) {
1219 	            rc = TRUE;
1220 	#endif
1221 	
1222 	#if SUPPORT_UPSTART
1223 	        } else if (upstart_job_exists(agent)) {
1224 	            rc = TRUE;
1225 	#endif
1226 	        } else {
1227 	            rc = FALSE;
1228 	        }
1229 	
1230 	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
1231 	        rc = services__ocf_agent_exists(provider, agent);
1232 	
1233 	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1234 	        rc = services__lsb_agent_exists(agent);
1235 	
1236 	#if SUPPORT_SYSTEMD
1237 	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
1238 	        rc = systemd_unit_exists(agent);
1239 	#endif
1240 	
1241 	#if SUPPORT_UPSTART
1242 	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
1243 	        rc = upstart_job_exists(agent);
1244 	#endif
1245 	
1246 	#if SUPPORT_NAGIOS
1247 	    } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1248 	        rc = services__nagios_agent_exists(agent);
1249 	#endif
1250 	
1251 	    } else {
1252 	        rc = FALSE;
1253 	    }
1254 	
1255 	done:
1256 	    g_list_free(standards);
1257 	    g_list_free(providers);
1258 	    return rc;
1259 	}
1260 	
1261 	/*!
1262 	 * \internal
1263 	 * \brief Set the result of an action
1264 	 *
1265 	 * \param[out] action        Where to set action result
1266 	 * \param[in]  agent_status  Exit status to set
1267 	 * \param[in]  exec_status   Execution status to set
1268 	 * \param[in]  reason        Human-friendly description of event to set
1269 	 */
1270 	void
1271 	services__set_result(svc_action_t *action, int agent_status,
1272 	                     enum pcmk_exec_status exec_status, const char *reason)
1273 	{
1274 	    if (action == NULL) {
1275 	        return;
1276 	    }
1277 	
1278 	    action->rc = agent_status;
1279 	    action->status = exec_status;
1280 	
1281 	    if (!pcmk__str_eq(action->opaque->exit_reason, reason,
1282 	                      pcmk__str_none)) {
1283 	        free(action->opaque->exit_reason);
1284 	        action->opaque->exit_reason = (reason == NULL)? NULL : strdup(reason);
1285 	    }
1286 	}
1287 	
1288 	/*!
1289 	 * \internal
1290 	 * \brief Set the result of an action, with a formatted exit reason
1291 	 *
1292 	 * \param[out] action        Where to set action result
1293 	 * \param[in]  agent_status  Exit status to set
1294 	 * \param[in]  exec_status   Execution status to set
1295 	 * \param[in]  format        printf-style format for a human-friendly
1296 	 *                           description of reason for result
1297 	 * \param[in]  ...           arguments for \p format
1298 	 */
1299 	void
1300 	services__format_result(svc_action_t *action, int agent_status,
1301 	                        enum pcmk_exec_status exec_status,
1302 	                        const char *format, ...)
1303 	{
1304 	    va_list ap;
1305 	    int len = 0;
1306 	    char *reason = NULL;
1307 	
1308 	    if (action == NULL) {
1309 	        return;
1310 	    }
1311 	
1312 	    action->rc = agent_status;
1313 	    action->status = exec_status;
1314 	
1315 	    if (format != NULL) {
1316 	        va_start(ap, format);
1317 	        len = vasprintf(&reason, format, ap);
1318 	        CRM_ASSERT(len > 0);
1319 	        va_end(ap);
1320 	    }
1321 	    free(action->opaque->exit_reason);
1322 	    action->opaque->exit_reason = reason;
1323 	}
1324 	
1325 	/*!
1326 	 * \internal
1327 	 * \brief Set the result of an action to cancelled
1328 	 *
1329 	 * \param[out] action        Where to set action result
1330 	 *
1331 	 * \note This sets execution status but leaves the exit status unchanged
1332 	 */
1333 	void
1334 	services__set_cancelled(svc_action_t *action)
1335 	{
1336 	    if (action != NULL) {
1337 	        action->status = PCMK_EXEC_CANCELLED;
1338 	        free(action->opaque->exit_reason);
1339 	        action->opaque->exit_reason = NULL;
1340 	    }
1341 	}
1342 	
1343 	/*!
1344 	 * \internal
1345 	 * \brief Get a readable description of what an action is for
1346 	 *
1347 	 * \param[in] action  Action to check
1348 	 *
1349 	 * \return Readable name for the kind of \p action
1350 	 */
1351 	const char *
1352 	services__action_kind(const svc_action_t *action)
1353 	{
1354 	    if ((action == NULL) || (action->standard == NULL)) {
1355 	        return "Process";
1356 	    } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_STONITH,
1357 	                            pcmk__str_none)) {
1358 	        return "Fence agent";
1359 	    } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_ALERT,
1360 	                            pcmk__str_none)) {
1361 	        return "Alert agent";
1362 	    } else {
1363 	        return "Resource agent";
1364 	    }
1365 	}
1366 	
1367 	/*!
1368 	 * \internal
1369 	 * \brief Get the exit reason of an action
1370 	 *
1371 	 * \param[in] action  Action to check
1372 	 *
1373 	 * \return Action's exit reason (or NULL if none)
1374 	 */
1375 	const char *
1376 	services__exit_reason(const svc_action_t *action)
1377 	{
1378 	    return action->opaque->exit_reason;
1379 	}
1380 	
1381 	/*!
1382 	 * \internal
1383 	 * \brief Steal stdout from an action
1384 	 *
1385 	 * \param[in,out] action  Action whose stdout is desired
1386 	 *
1387 	 * \return Action's stdout (which may be NULL)
1388 	 * \note Upon return, \p action will no longer track the output, so it is the
1389 	 *       caller's responsibility to free the return value.
1390 	 */
1391 	char *
1392 	services__grab_stdout(svc_action_t *action)
1393 	{
1394 	    char *output = action->stdout_data;
1395 	
1396 	    action->stdout_data = NULL;
1397 	    return output;
1398 	}
1399 	
1400 	/*!
1401 	 * \internal
1402 	 * \brief Steal stderr from an action
1403 	 *
1404 	 * \param[in,out] action  Action whose stderr is desired
1405 	 *
1406 	 * \return Action's stderr (which may be NULL)
1407 	 * \note Upon return, \p action will no longer track the output, so it is the
1408 	 *       caller's responsibility to free the return value.
1409 	 */
1410 	char *
1411 	services__grab_stderr(svc_action_t *action)
1412 	{
1413 	    char *output = action->stderr_data;
1414 	
1415 	    action->stderr_data = NULL;
1416 	    return output;
1417 	}
1418