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(¶ms, 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(¶ms, 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