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