1 /*
2  * Copyright 2010-2021 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_lsb.h"
32 
33 #if SUPPORT_UPSTART
34 #  include <upstart.h>
35 #endif
36 
37 #if SUPPORT_SYSTEMD
38 #  include <systemd.h>
39 #endif
40 
41 #if SUPPORT_NAGIOS
42 #  include <services_nagios.h>
43 #endif
44 
45 /* TODO: Develop a rollover strategy */
46 
47 static int operations = 0;
48 static GHashTable *recurring_actions = NULL;
49 
50 /* ops waiting to run async because of conflicting active
51  * pending ops */
52 static GList *blocked_ops = NULL;
53 
54 /* ops currently active (in-flight) */
55 static GList *inflight_ops = NULL;
56 
57 static void handle_blocked_ops(void);
58 
59 /*!
60  * \brief Find first service class that can provide a specified agent
61  *
62  * \param[in] agent  Name of agent to search for
63  *
64  * \return Service class if found, NULL otherwise
65  *
66  * \note The priority is LSB, then systemd, then upstart. It would be preferable
67  *       to put systemd first, but LSB merely requires a file existence check,
68  *       while systemd requires contacting D-Bus.
69  */
70 const char *
resources_find_service_class(const char * agent)71 resources_find_service_class(const char *agent)
72 {
73     if (services__lsb_agent_exists(agent)) {
74         return PCMK_RESOURCE_CLASS_LSB;
75     }
76 
77 #if SUPPORT_SYSTEMD
78     if (systemd_unit_exists(agent)) {
79         return PCMK_RESOURCE_CLASS_SYSTEMD;
80     }
81 #endif
82 
83 #if SUPPORT_UPSTART
84     if (upstart_job_exists(agent)) {
85         return PCMK_RESOURCE_CLASS_UPSTART;
86     }
87 #endif
88     return NULL;
89 }
90 
91 static inline void
init_recurring_actions(void)92 init_recurring_actions(void)
93 {
94     if (recurring_actions == NULL) {
95         recurring_actions = pcmk__strkey_table(NULL, NULL);
96     }
97 }
98 
99 /*!
100  * \internal
101  * \brief Check whether op is in-flight systemd or upstart op
102  *
103  * \param[in] op  Operation to check
104  *
105  * \return TRUE if op is in-flight systemd or upstart op
106  */
107 static inline gboolean
inflight_systemd_or_upstart(svc_action_t * op)108 inflight_systemd_or_upstart(svc_action_t *op)
109 {
110     return pcmk__strcase_any_of(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
111                            PCMK_RESOURCE_CLASS_UPSTART, NULL) &&
112            g_list_find(inflight_ops, op) != NULL;
113 }
114 
115 /*!
116  * \internal
117  * \brief Expand "service" alias to an actual resource class
118  *
119  * \param[in] rsc       Resource name (for logging only)
120  * \param[in] standard  Resource class as configured
121  * \param[in] agent     Agent name to look for
122  *
123  * \return Newly allocated string with actual resource class
124  *
125  * \note The caller is responsible for calling free() on the result.
126  */
127 static char *
expand_resource_class(const char * rsc,const char * standard,const char * agent)128 expand_resource_class(const char *rsc, const char *standard, const char *agent)
129 {
130     char *expanded_class = NULL;
131 
132     if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
133         const char *found_class = resources_find_service_class(agent);
134 
135         if (found_class) {
136             crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
137             expanded_class = strdup(found_class);
138         } else {
139             crm_info("Assuming resource class lsb for agent %s for %s",
140                      agent, rsc);
141             expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
142         }
143     } else {
144         expanded_class = strdup(standard);
145     }
146     CRM_ASSERT(expanded_class);
147     return expanded_class;
148 }
149 
150 #if SUPPORT_NAGIOS
151 /*!
152  * \brief Duplicate a file path, inserting a prefix if not absolute
153  *
154  * \param[in] filename  File path to duplicate
155  * \param[in] dirname   If filename is not absolute, prefix to add
156  *
157  * \return Newly allocated memory with full path
158  */
159 static char *
dup_file_path(const char * filename,const char * dirname)160 dup_file_path(const char *filename, const char *dirname)
161 {
162     return (*filename == '/')? strdup(filename)
163            : crm_strdup_printf("%s/%s", dirname, filename);
164 }
165 #endif
166 
167 svc_action_t *
services__create_resource_action(const char * name,const char * standard,const char * provider,const char * agent,const char * action,guint interval_ms,int timeout,GHashTable * params,enum svc_action_flags flags)168 services__create_resource_action(const char *name, const char *standard,
169                         const char *provider, const char *agent,
170                         const char *action, guint interval_ms, int timeout,
171                         GHashTable *params, enum svc_action_flags flags)
172 {
173     svc_action_t *op = NULL;
174     uint32_t ra_caps = 0;
175 
176     /*
177      * Do some up front sanity checks before we go off and
178      * build the svc_action_t instance.
179      */
180 
181     if (pcmk__str_empty(name)) {
182         crm_err("Cannot create operation without resource name");
183         goto return_error;
184     }
185 
186     if (pcmk__str_empty(standard)) {
187         crm_err("Cannot create operation for %s without resource class", name);
188         goto return_error;
189     }
190     ra_caps = pcmk_get_ra_caps(standard);
191 
192     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)
193         && pcmk__str_empty(provider)) {
194         crm_err("Cannot create operation for %s without provider", name);
195         goto return_error;
196     }
197 
198     if (pcmk__str_empty(agent)) {
199         crm_err("Cannot create operation for %s without agent name", name);
200         goto return_error;
201     }
202 
203     if (pcmk__str_empty(action)) {
204         crm_err("Cannot create operation for %s without operation name", name);
205         goto return_error;
206     }
207 
208     /*
209      * Sanity checks passed, proceed!
210      */
211 
212     op = calloc(1, sizeof(svc_action_t));
213     op->opaque = calloc(1, sizeof(svc_action_private_t));
214     op->rsc = strdup(name);
215     op->interval_ms = interval_ms;
216     op->timeout = timeout;
217     op->standard = expand_resource_class(name, standard, agent);
218     op->agent = strdup(agent);
219     op->sequence = ++operations;
220     op->flags = flags;
221     op->id = pcmk__op_key(name, action, interval_ms);
222 
223     if (pcmk_is_set(ra_caps, pcmk_ra_cap_status)
224         && pcmk__str_eq(action, "monitor", pcmk__str_casei)) {
225 
226         op->action = strdup("status");
227     } else {
228         op->action = strdup(action);
229     }
230 
231     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)) {
232         op->provider = strdup(provider);
233     }
234 
235     if (pcmk_is_set(ra_caps, pcmk_ra_cap_params)) {
236         op->params = params;
237         params = NULL; // so we don't free them in this function
238     }
239 
240     if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
241         char *dirs = strdup(OCF_RA_PATH);
242         char *dir = NULL;
243         char *buf = NULL;
244         struct stat st;
245 
246         if (pcmk__str_empty(dirs)) {
247             free(dirs);
248             services__handle_exec_error(op, ENOMEM);
249             return op;
250         }
251 
252         for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
253             buf = crm_strdup_printf("%s/%s/%s", dir, provider, agent);
254             if (stat(buf, &st) == 0) {
255                 break;
256             }
257             free(buf);
258             buf = NULL;
259         }
260 
261         free(dirs);
262 
263         if (buf) {
264             op->opaque->exec = buf;
265         } else {
266             services__handle_exec_error(op, ENOENT);
267             return op;
268         }
269 
270         op->opaque->args[0] = strdup(op->opaque->exec);
271         op->opaque->args[1] = strdup(op->action);
272 
273     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
274         op->opaque->exec = services__lsb_agent_path(op->agent);
275         op->opaque->args[0] = strdup(op->opaque->exec);
276         op->opaque->args[1] = strdup(op->action);
277 
278 #if SUPPORT_SYSTEMD
279     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
280         op->opaque->exec = strdup("systemd-dbus");
281 #endif
282 #if SUPPORT_UPSTART
283     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
284         op->opaque->exec = strdup("upstart-dbus");
285 #endif
286 #if SUPPORT_NAGIOS
287     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
288         op->opaque->exec = dup_file_path(op->agent, NAGIOS_PLUGIN_DIR);
289         op->opaque->args[0] = strdup(op->opaque->exec);
290 
291         if (pcmk__str_eq(op->action, "monitor", pcmk__str_casei) && (op->interval_ms == 0)) {
292             /* Invoke --version for a nagios probe */
293             op->opaque->args[1] = strdup("--version");
294 
295         } else if (op->params) {
296             GHashTableIter iter;
297             char *key = NULL;
298             char *value = NULL;
299             int index = 1;
300             static int args_size = sizeof(op->opaque->args) / sizeof(char *);
301 
302             g_hash_table_iter_init(&iter, op->params);
303 
304             while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value) &&
305                    index <= args_size - 3) {
306 
307                 if (pcmk__str_eq(key, XML_ATTR_CRM_VERSION, pcmk__str_casei) || strstr(key, CRM_META "_")) {
308                     continue;
309                 }
310                 op->opaque->args[index++] = crm_strdup_printf("--%s", key);
311                 op->opaque->args[index++] = strdup(value);
312             }
313         }
314 
315         // Nagios actions don't need to keep the parameters
316         if (op->params != NULL) {
317             g_hash_table_destroy(op->params);
318             op->params = NULL;
319         }
320 #endif
321     } else {
322         crm_err("Unknown resource standard: %s", op->standard);
323         services__handle_exec_error(op, ENOENT);
324     }
325 
326   return_error:
327     if(params) {
328         g_hash_table_destroy(params);
329     }
330 
331     return op;
332 }
333 
334 svc_action_t *
resources_action_create(const char * name,const char * standard,const char * provider,const char * agent,const char * action,guint interval_ms,int timeout,GHashTable * params,enum svc_action_flags flags)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         return op;
348     }
349 }
350 
351 svc_action_t *
services_action_create_generic(const char * exec,const char * args[])352 services_action_create_generic(const char *exec, const char *args[])
353 {
354     svc_action_t *op;
355     unsigned int cur_arg;
356 
357     op = calloc(1, sizeof(*op));
358     op->opaque = calloc(1, sizeof(svc_action_private_t));
359 
360     op->opaque->exec = strdup(exec);
361     op->opaque->args[0] = strdup(exec);
362 
363     for (cur_arg = 1; args && args[cur_arg - 1]; cur_arg++) {
364         op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
365 
366         if (cur_arg == PCMK__NELEM(op->opaque->args) - 1) {
367             crm_err("svc_action_t args list not long enough for '%s' execution request.", exec);
368             break;
369         }
370     }
371 
372     return op;
373 }
374 
375 /*!
376  * \brief Create an alert agent action
377  *
378  * \param[in] id        Alert ID
379  * \param[in] exec      Path to alert agent executable
380  * \param[in] timeout   Action timeout
381  * \param[in] params    Parameters to use with action
382  * \param[in] sequence  Action sequence number
383  * \param[in] cb_data   Data to pass to callback function
384  *
385  * \return New action on success, NULL on error
386  * \note It is the caller's responsibility to free cb_data.
387  *       The caller should not free params explicitly.
388  */
389 svc_action_t *
services_alert_create(const char * id,const char * exec,int timeout,GHashTable * params,int sequence,void * cb_data)390 services_alert_create(const char *id, const char *exec, int timeout,
391                       GHashTable *params, int sequence, void *cb_data)
392 {
393     svc_action_t *action = services_action_create_generic(exec, NULL);
394 
395     CRM_ASSERT(action);
396     action->timeout = timeout;
397     action->id = strdup(id);
398     action->params = params;
399     action->sequence = sequence;
400     action->cb_data = cb_data;
401     return action;
402 }
403 
404 /*!
405  * \brief Set the user and group that an action will execute as
406  *
407  * \param[in,out] action  Action to modify
408  * \param[in]     user    Name of user to execute action as
409  * \param[in]     group   Name of group to execute action as
410  *
411  * \return pcmk_ok on success, -errno otherwise
412  *
413  * \note This will have no effect unless the process executing the action runs
414  *       as root, and the action is not a systemd or upstart action.
415  *       We could implement this for systemd by adding User= and Group= to
416  *       [Service] in the override file, but that seems more likely to cause
417  *       problems than be useful.
418  */
419 int
services_action_user(svc_action_t * op,const char * user)420 services_action_user(svc_action_t *op, const char *user)
421 {
422     CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
423     return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
424 }
425 
426 /*!
427  * \brief Execute an alert agent action
428  *
429  * \param[in] action  Action to execute
430  * \param[in] cb      Function to call when action completes
431  *
432  * \return TRUE if the library will free action, FALSE otherwise
433  *
434  * \note If this function returns FALSE, it is the caller's responsibility to
435  *       free the action with services_action_free().
436  */
437 gboolean
services_alert_async(svc_action_t * action,void (* cb)(svc_action_t * op))438 services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
439 {
440     action->synchronous = false;
441     action->opaque->callback = cb;
442     return services_os_action_execute(action);
443 }
444 
445 #if SUPPORT_DBUS
446 /*!
447  * \internal
448  * \brief Update operation's pending DBus call, unreferencing old one if needed
449  *
450  * \param[in,out] op       Operation to modify
451  * \param[in]     pending  Pending call to set
452  */
453 void
services_set_op_pending(svc_action_t * op,DBusPendingCall * pending)454 services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
455 {
456     if (op->opaque->pending && (op->opaque->pending != pending)) {
457         if (pending) {
458             crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
459         } else {
460             crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
461         }
462         dbus_pending_call_unref(op->opaque->pending);
463     }
464     op->opaque->pending = pending;
465     if (pending) {
466         crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
467     } else {
468         crm_trace("Cleared pending %s DBus call", op->id);
469     }
470 }
471 #endif
472 
473 void
services_action_cleanup(svc_action_t * op)474 services_action_cleanup(svc_action_t * op)
475 {
476     if ((op == NULL) || (op->opaque == NULL)) {
477         return;
478     }
479 
480 #if SUPPORT_DBUS
481     if(op->opaque->timerid != 0) {
482         crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
483         g_source_remove(op->opaque->timerid);
484         op->opaque->timerid = 0;
485     }
486 
487     if(op->opaque->pending) {
488         if (dbus_pending_call_get_completed(op->opaque->pending)) {
489             // This should never be the case
490             crm_warn("Result of %s op %s was unhandled",
491                      op->standard, op->id);
492         } else {
493             crm_debug("Will ignore any result of canceled %s op %s",
494                       op->standard, op->id);
495         }
496         dbus_pending_call_cancel(op->opaque->pending);
497         services_set_op_pending(op, NULL);
498     }
499 #endif
500 
501     if (op->opaque->stderr_gsource) {
502         mainloop_del_fd(op->opaque->stderr_gsource);
503         op->opaque->stderr_gsource = NULL;
504     }
505 
506     if (op->opaque->stdout_gsource) {
507         mainloop_del_fd(op->opaque->stdout_gsource);
508         op->opaque->stdout_gsource = NULL;
509     }
510 }
511 
512 void
services_action_free(svc_action_t * op)513 services_action_free(svc_action_t * op)
514 {
515     unsigned int i;
516 
517     if (op == NULL) {
518         return;
519     }
520 
521     /* The operation should be removed from all tracking lists by this point.
522      * If it's not, we have a bug somewhere, so bail. That may lead to a
523      * memory leak, but it's better than a use-after-free segmentation fault.
524      */
525     CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
526     CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
527     CRM_CHECK((recurring_actions == NULL)
528               || (g_hash_table_lookup(recurring_actions, op->id) == NULL),
529               return);
530 
531     services_action_cleanup(op);
532 
533     if (op->opaque->repeat_timer) {
534         g_source_remove(op->opaque->repeat_timer);
535         op->opaque->repeat_timer = 0;
536     }
537 
538     free(op->id);
539     free(op->opaque->exec);
540 
541     for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
542         free(op->opaque->args[i]);
543     }
544 
545     free(op->opaque);
546     free(op->rsc);
547     free(op->action);
548 
549     free(op->standard);
550     free(op->agent);
551     free(op->provider);
552 
553     free(op->stdout_data);
554     free(op->stderr_data);
555 
556     if (op->params) {
557         g_hash_table_destroy(op->params);
558         op->params = NULL;
559     }
560 
561     free(op);
562 }
563 
564 gboolean
cancel_recurring_action(svc_action_t * op)565 cancel_recurring_action(svc_action_t * op)
566 {
567     crm_info("Cancelling %s operation %s", op->standard, op->id);
568 
569     if (recurring_actions) {
570         g_hash_table_remove(recurring_actions, op->id);
571     }
572 
573     if (op->opaque->repeat_timer) {
574         g_source_remove(op->opaque->repeat_timer);
575         op->opaque->repeat_timer = 0;
576     }
577 
578     return TRUE;
579 }
580 
581 /*!
582  * \brief Cancel a recurring action
583  *
584  * \param[in] name         Name of resource that operation is for
585  * \param[in] action       Name of operation to cancel
586  * \param[in] interval_ms  Interval of operation to cancel
587  *
588  * \return TRUE if action was successfully cancelled, FALSE otherwise
589  */
590 gboolean
services_action_cancel(const char * name,const char * action,guint interval_ms)591 services_action_cancel(const char *name, const char *action, guint interval_ms)
592 {
593     gboolean cancelled = FALSE;
594     char *id = pcmk__op_key(name, action, interval_ms);
595     svc_action_t *op = NULL;
596 
597     /* We can only cancel a recurring action */
598     init_recurring_actions();
599     op = g_hash_table_lookup(recurring_actions, id);
600     if (op == NULL) {
601         goto done;
602     }
603 
604     /* Tell operation_finalize() not to reschedule the operation */
605     op->cancel = TRUE;
606 
607     /* Stop tracking it as a recurring operation, and stop its repeat timer */
608     cancel_recurring_action(op);
609 
610     /* If the op has a PID, it's an in-flight child process, so kill it.
611      *
612      * Whether the kill succeeds or fails, the main loop will send the op to
613      * operation_finished() (and thus operation_finalize()) when the process
614      * goes away.
615      */
616     if (op->pid != 0) {
617         crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
618                  id, op->pid);
619         cancelled = mainloop_child_kill(op->pid);
620         if (cancelled == FALSE) {
621             crm_err("Termination of %s[%d] failed", id, op->pid);
622         }
623         goto done;
624     }
625 
626 #if SUPPORT_DBUS
627     // In-flight systemd and upstart ops don't have a pid
628     if (inflight_systemd_or_upstart(op)) {
629         inflight_ops = g_list_remove(inflight_ops, op);
630 
631         /* This will cause any result that comes in later to be discarded, so we
632          * don't call the callback and free the operation twice.
633          */
634         services_action_cleanup(op);
635     }
636 #endif
637 
638     // The rest of this is essentially equivalent to operation_finalize(),
639     // except without calling handle_blocked_ops()
640 
641     // Report operation as cancelled
642     op->status = PCMK_LRM_OP_CANCELLED;
643     if (op->opaque->callback) {
644         op->opaque->callback(op);
645     }
646 
647     blocked_ops = g_list_remove(blocked_ops, op);
648     services_action_free(op);
649     cancelled = TRUE;
650     // @TODO Initiate handle_blocked_ops() asynchronously
651 
652 done:
653     free(id);
654     return cancelled;
655 }
656 
657 gboolean
services_action_kick(const char * name,const char * action,guint interval_ms)658 services_action_kick(const char *name, const char *action, guint interval_ms)
659 {
660     svc_action_t * op = NULL;
661     char *id = pcmk__op_key(name, action, interval_ms);
662 
663     init_recurring_actions();
664     op = g_hash_table_lookup(recurring_actions, id);
665     free(id);
666 
667     if (op == NULL) {
668         return FALSE;
669     }
670 
671 
672     if (op->pid || inflight_systemd_or_upstart(op)) {
673         return TRUE;
674     } else {
675         if (op->opaque->repeat_timer) {
676             g_source_remove(op->opaque->repeat_timer);
677             op->opaque->repeat_timer = 0;
678         }
679         recurring_action_timer(op);
680         return TRUE;
681     }
682 
683 }
684 
685 /*!
686  * \internal
687  * \brief Add a new recurring operation, checking for duplicates
688  *
689  * \param[in] op               Operation to add
690  *
691  * \return TRUE if duplicate found (and reschedule), FALSE otherwise
692  */
693 static gboolean
handle_duplicate_recurring(svc_action_t * op)694 handle_duplicate_recurring(svc_action_t * op)
695 {
696     svc_action_t * dup = NULL;
697 
698     /* check for duplicates */
699     dup = g_hash_table_lookup(recurring_actions, op->id);
700 
701     if (dup && (dup != op)) {
702         /* update user data */
703         if (op->opaque->callback) {
704             dup->opaque->callback = op->opaque->callback;
705             dup->cb_data = op->cb_data;
706             op->cb_data = NULL;
707         }
708         /* immediately execute the next interval */
709         if (dup->pid != 0) {
710             if (op->opaque->repeat_timer) {
711                 g_source_remove(op->opaque->repeat_timer);
712                 op->opaque->repeat_timer = 0;
713             }
714             recurring_action_timer(dup);
715         }
716         /* free the duplicate */
717         services_action_free(op);
718         return TRUE;
719     }
720 
721     return FALSE;
722 }
723 
724 inline static gboolean
action_exec_helper(svc_action_t * op)725 action_exec_helper(svc_action_t * op)
726 {
727     /* Whether a/synchronous must be decided (op->synchronous) beforehand. */
728     if (op->standard
729         && (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0)) {
730 #if SUPPORT_UPSTART
731         return upstart_job_exec(op);
732 #endif
733     } else if (op->standard && strcasecmp(op->standard,
734                                           PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
735 #if SUPPORT_SYSTEMD
736         return systemd_unit_exec(op);
737 #endif
738     } else {
739         return services_os_action_execute(op);
740     }
741     /* The 'op' has probably been freed if the execution functions return TRUE
742        for the asynchronous 'op'. */
743     /* Avoid using the 'op' in here. */
744 
745     return FALSE;
746 }
747 
748 void
services_add_inflight_op(svc_action_t * op)749 services_add_inflight_op(svc_action_t * op)
750 {
751     if (op == NULL) {
752         return;
753     }
754 
755     CRM_ASSERT(op->synchronous == FALSE);
756 
757     /* keep track of ops that are in-flight to avoid collisions in the same namespace */
758     if (op->rsc) {
759         inflight_ops = g_list_append(inflight_ops, op);
760     }
761 }
762 
763 /*!
764  * \internal
765  * \brief Stop tracking an operation that completed
766  *
767  * \param[in] op  Operation to stop tracking
768  */
769 void
services_untrack_op(svc_action_t * op)770 services_untrack_op(svc_action_t *op)
771 {
772     /* Op is no longer in-flight or blocked */
773     inflight_ops = g_list_remove(inflight_ops, op);
774     blocked_ops = g_list_remove(blocked_ops, op);
775 
776     /* Op is no longer blocking other ops, so check if any need to run */
777     handle_blocked_ops();
778 }
779 
780 gboolean
services_action_async_fork_notify(svc_action_t * op,void (* action_callback)(svc_action_t *),void (* action_fork_callback)(svc_action_t *))781 services_action_async_fork_notify(svc_action_t * op,
782                                   void (*action_callback) (svc_action_t *),
783                                   void (*action_fork_callback) (svc_action_t *))
784 {
785     op->synchronous = false;
786     if (action_callback) {
787         op->opaque->callback = action_callback;
788     }
789     if (action_fork_callback) {
790         op->opaque->fork_callback = action_fork_callback;
791     }
792 
793     if (op->interval_ms > 0) {
794         init_recurring_actions();
795         if (handle_duplicate_recurring(op) == TRUE) {
796             /* entry rescheduled, dup freed */
797             /* exit early */
798             return TRUE;
799         }
800         g_hash_table_replace(recurring_actions, op->id, op);
801     }
802 
803     if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
804         && op->rsc && is_op_blocked(op->rsc)) {
805         blocked_ops = g_list_append(blocked_ops, op);
806         return TRUE;
807     }
808 
809     return action_exec_helper(op);
810 }
811 
812 gboolean
services_action_async(svc_action_t * op,void (* action_callback)(svc_action_t *))813 services_action_async(svc_action_t * op,
814                       void (*action_callback) (svc_action_t *))
815 {
816     return services_action_async_fork_notify(op, action_callback, NULL);
817 }
818 
819 static gboolean processing_blocked_ops = FALSE;
820 
821 gboolean
is_op_blocked(const char * rsc)822 is_op_blocked(const char *rsc)
823 {
824     GList *gIter = NULL;
825     svc_action_t *op = NULL;
826 
827     for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
828         op = gIter->data;
829         if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
830             return TRUE;
831         }
832     }
833 
834     return FALSE;
835 }
836 
837 static void
handle_blocked_ops(void)838 handle_blocked_ops(void)
839 {
840     GList *executed_ops = NULL;
841     GList *gIter = NULL;
842     svc_action_t *op = NULL;
843     gboolean res = FALSE;
844 
845     if (processing_blocked_ops) {
846         /* avoid nested calling of this function */
847         return;
848     }
849 
850     processing_blocked_ops = TRUE;
851 
852     /* n^2 operation here, but blocked ops are incredibly rare. this list
853      * will be empty 99% of the time. */
854     for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
855         op = gIter->data;
856         if (is_op_blocked(op->rsc)) {
857             continue;
858         }
859         executed_ops = g_list_append(executed_ops, op);
860         res = action_exec_helper(op);
861         if (res == FALSE) {
862             op->status = PCMK_LRM_OP_ERROR;
863             /* this can cause this function to be called recursively
864              * which is why we have processing_blocked_ops static variable */
865             operation_finalize(op);
866         }
867     }
868 
869     for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
870         op = gIter->data;
871         blocked_ops = g_list_remove(blocked_ops, op);
872     }
873     g_list_free(executed_ops);
874 
875     processing_blocked_ops = FALSE;
876 }
877 
878 static gboolean
action_get_metadata(svc_action_t * op)879 action_get_metadata(svc_action_t *op)
880 {
881     const char *class = op->standard;
882 
883     if (op->agent == NULL) {
884         crm_err("meta-data requested without specifying agent");
885         return FALSE;
886     }
887 
888     if (class == NULL) {
889         crm_err("meta-data requested for agent %s without specifying class",
890                 op->agent);
891         return FALSE;
892     }
893 
894     if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
895         class = resources_find_service_class(op->agent);
896     }
897 
898     if (class == NULL) {
899         crm_err("meta-data requested for %s, but could not determine class",
900                 op->agent);
901         return FALSE;
902     }
903 
904     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
905         return (services__get_lsb_metadata(op->agent, &op->stdout_data) >= 0);
906     }
907 
908 #if SUPPORT_NAGIOS
909     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
910         return services__get_nagios_metadata(op->agent, &op->stdout_data) >= 0;
911     }
912 #endif
913 
914     return action_exec_helper(op);
915 }
916 
917 gboolean
services_action_sync(svc_action_t * op)918 services_action_sync(svc_action_t * op)
919 {
920     gboolean rc = TRUE;
921 
922     if (op == NULL) {
923         crm_trace("No operation to execute");
924         return FALSE;
925     }
926 
927     op->synchronous = true;
928 
929     if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) {
930         /* Synchronous meta-data operations are handled specially. Since most
931          * resource classes don't provide any meta-data, it has to be
932          * synthesized from available information about the agent.
933          *
934          * services_action_async() doesn't treat meta-data actions specially, so
935          * it will result in an error for classes that don't support the action.
936          */
937         rc = action_get_metadata(op);
938     } else {
939         rc = action_exec_helper(op);
940     }
941     crm_trace(" > " PCMK__OP_FMT ": %s = %d",
942               op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
943     if (op->stdout_data) {
944         crm_trace(" >  stdout: %s", op->stdout_data);
945     }
946     if (op->stderr_data) {
947         crm_trace(" >  stderr: %s", op->stderr_data);
948     }
949     return rc;
950 }
951 
952 GList *
get_directory_list(const char * root,gboolean files,gboolean executable)953 get_directory_list(const char *root, gboolean files, gboolean executable)
954 {
955     return services_os_get_directory_list(root, files, executable);
956 }
957 
958 GList *
resources_list_standards(void)959 resources_list_standards(void)
960 {
961     GList *standards = NULL;
962 
963     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
964     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
965     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
966 
967 #if SUPPORT_SYSTEMD
968     {
969         GList *agents = systemd_unit_listall();
970 
971         if (agents != NULL) {
972             standards = g_list_append(standards,
973                                       strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
974             g_list_free_full(agents, free);
975         }
976     }
977 #endif
978 
979 #if SUPPORT_UPSTART
980     {
981         GList *agents = upstart_job_listall();
982 
983         if (agents != NULL) {
984             standards = g_list_append(standards,
985                                       strdup(PCMK_RESOURCE_CLASS_UPSTART));
986             g_list_free_full(agents, free);
987         }
988     }
989 #endif
990 
991 #if SUPPORT_NAGIOS
992     {
993         GList *agents = services__list_nagios_agents();
994 
995         if (agents != NULL) {
996             standards = g_list_append(standards,
997                                       strdup(PCMK_RESOURCE_CLASS_NAGIOS));
998             g_list_free_full(agents, free);
999         }
1000     }
1001 #endif
1002 
1003     return standards;
1004 }
1005 
1006 GList *
resources_list_providers(const char * standard)1007 resources_list_providers(const char *standard)
1008 {
1009     if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
1010         return resources_os_list_ocf_providers();
1011     }
1012 
1013     return NULL;
1014 }
1015 
1016 GList *
resources_list_agents(const char * standard,const char * provider)1017 resources_list_agents(const char *standard, const char *provider)
1018 {
1019     if ((standard == NULL)
1020         || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
1021 
1022         GList *tmp1;
1023         GList *tmp2;
1024         GList *result = services__list_lsb_agents();
1025 
1026         if (standard == NULL) {
1027             tmp1 = result;
1028             tmp2 = resources_os_list_ocf_agents(NULL);
1029             if (tmp2) {
1030                 result = g_list_concat(tmp1, tmp2);
1031             }
1032         }
1033 #if SUPPORT_SYSTEMD
1034         tmp1 = result;
1035         tmp2 = systemd_unit_listall();
1036         if (tmp2) {
1037             result = g_list_concat(tmp1, tmp2);
1038         }
1039 #endif
1040 
1041 #if SUPPORT_UPSTART
1042         tmp1 = result;
1043         tmp2 = upstart_job_listall();
1044         if (tmp2) {
1045             result = g_list_concat(tmp1, tmp2);
1046         }
1047 #endif
1048 
1049         return result;
1050 
1051     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
1052         return resources_os_list_ocf_agents(provider);
1053     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
1054         return services__list_lsb_agents();
1055 #if SUPPORT_SYSTEMD
1056     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
1057         return systemd_unit_listall();
1058 #endif
1059 #if SUPPORT_UPSTART
1060     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
1061         return upstart_job_listall();
1062 #endif
1063 #if SUPPORT_NAGIOS
1064     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
1065         return services__list_nagios_agents();
1066 #endif
1067     }
1068 
1069     return NULL;
1070 }
1071 
1072 gboolean
resources_agent_exists(const char * standard,const char * provider,const char * agent)1073 resources_agent_exists(const char *standard, const char *provider, const char *agent)
1074 {
1075     GList *standards = NULL;
1076     GList *providers = NULL;
1077     GList *iter = NULL;
1078     gboolean rc = FALSE;
1079     gboolean has_providers = FALSE;
1080 
1081     standards = resources_list_standards();
1082     for (iter = standards; iter != NULL; iter = iter->next) {
1083         if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
1084             rc = TRUE;
1085             break;
1086         }
1087     }
1088 
1089     if (rc == FALSE) {
1090         goto done;
1091     }
1092 
1093     rc = FALSE;
1094 
1095     has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
1096     if (has_providers == TRUE && provider != NULL) {
1097         providers = resources_list_providers(standard);
1098         for (iter = providers; iter != NULL; iter = iter->next) {
1099             if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
1100                 rc = TRUE;
1101                 break;
1102             }
1103         }
1104     } else if (has_providers == FALSE && provider == NULL) {
1105         rc = TRUE;
1106     }
1107 
1108     if (rc == FALSE) {
1109         goto done;
1110     }
1111 
1112     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
1113         if (services__lsb_agent_exists(agent)) {
1114             rc = TRUE;
1115 #if SUPPORT_SYSTEMD
1116         } else if (systemd_unit_exists(agent)) {
1117             rc = TRUE;
1118 #endif
1119 
1120 #if SUPPORT_UPSTART
1121         } else if (upstart_job_exists(agent)) {
1122             rc = TRUE;
1123 #endif
1124         } else {
1125             rc = FALSE;
1126         }
1127 
1128     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
1129         rc = services__ocf_agent_exists(provider, agent);
1130 
1131     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1132         rc = services__lsb_agent_exists(agent);
1133 
1134 #if SUPPORT_SYSTEMD
1135     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
1136         rc = systemd_unit_exists(agent);
1137 #endif
1138 
1139 #if SUPPORT_UPSTART
1140     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
1141         rc = upstart_job_exists(agent);
1142 #endif
1143 
1144 #if SUPPORT_NAGIOS
1145     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1146         rc = services__nagios_agent_exists(agent);
1147 #endif
1148 
1149     } else {
1150         rc = FALSE;
1151     }
1152 
1153 done:
1154     g_list_free(standards);
1155     g_list_free(providers);
1156     return rc;
1157 }
1158