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