1 /*
2 * Copyright 2012-2025 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 #include <crm/crm.h>
12 #include <crm/common/xml.h>
13 #include <crm/services.h>
14 #include <crm/services_internal.h>
15 #include <crm/common/mainloop.h>
16
17 #include <dbus/dbus.h>
18 #include <inttypes.h> // PRIu32
19 #include <stdbool.h>
20 #include <stdint.h> // uint32_t
21 #include <stdio.h> // fopen(), NULL, etc.
22 #include <sys/stat.h>
23
24 #include <gio/gio.h>
25 #include <glib.h> // g_str_has_suffix()
26
27 #include <services_private.h>
28 #include <systemd.h>
29 #include <pcmk-dbus.h>
30
31 static void invoke_unit_by_path(svc_action_t *op, const char *unit);
32
33 /* Systemd D-Bus interface
34 * https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html
35 */
36 #define BUS_NAME "org.freedesktop.systemd1"
37 #define BUS_NAME_MANAGER BUS_NAME ".Manager"
38 #define BUS_NAME_UNIT BUS_NAME ".Unit"
39 #define BUS_PATH "/org/freedesktop/systemd1"
40
41 /*!
42 * \internal
43 * \brief Prepare a systemd action
44 *
45 * \param[in,out] op Action to prepare
46 *
47 * \return Standard Pacemaker return code
48 */
49 int
50 services__systemd_prepare(svc_action_t *op)
51 {
52 op->opaque->exec = strdup("systemd-dbus");
53 if (op->opaque->exec == NULL) {
54 return ENOMEM;
55 }
56 return pcmk_rc_ok;
57 }
58
59 /*!
60 * \internal
61 * \brief Map a systemd result to a standard OCF result
62 *
63 * \param[in] exit_status Systemd result
64 *
65 * \return Standard OCF result
66 */
67 enum ocf_exitcode
68 services__systemd2ocf(int exit_status)
69 {
70 // This library uses OCF codes for systemd actions
71 return (enum ocf_exitcode) exit_status;
72 }
73
74 static inline DBusMessage *
75 systemd_new_method(const char *method)
76 {
77 pcmk__trace("Calling: %s on " BUS_NAME_MANAGER, method);
78 return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER,
79 method);
80 }
81
82 /*
83 * Functions to manage a static DBus connection
84 */
85
86 static DBusConnection* systemd_proxy = NULL;
87
88 static inline DBusPendingCall *
89 systemd_send(DBusMessage *msg,
90 void(*done)(DBusPendingCall *pending, void *user_data),
91 void *user_data, int timeout)
92 {
93 return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout);
94 }
95
96 static inline DBusMessage *
97 systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout)
98 {
99 return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout);
100 }
101
102 /*!
103 * \internal
104 * \brief Send a method to systemd without arguments, and wait for reply
105 *
106 * \param[in] method Method to send
107 *
108 * \return Systemd reply on success, NULL (and error will be logged) otherwise
109 *
110 * \note The caller must call dbus_message_unref() on the reply after
111 * handling it.
112 */
113 static DBusMessage *
114 systemd_call_simple_method(const char *method)
115 {
116 DBusMessage *msg = NULL;
117 DBusMessage *reply = NULL;
118 DBusError error;
119
120 /* Don't call systemd_init() here, because that calls this */
121 CRM_CHECK(systemd_proxy, return NULL);
122
123 msg = systemd_new_method(method);
124
125 if (msg == NULL) {
126 pcmk__err("Could not create message to send %s to systemd", method);
127 return NULL;
128 }
129
130 dbus_error_init(&error);
131 reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT);
132 dbus_message_unref(msg);
133
134 if (dbus_error_is_set(&error)) {
135 pcmk__err("Could not send %s to systemd: %s (%s)", method,
136 error.message, error.name);
137 dbus_error_free(&error);
138 return NULL;
139
140 } else if (reply == NULL) {
141 pcmk__err("Could not send %s to systemd: no reply received", method);
142 return NULL;
143 }
144
145 return reply;
146 }
147
148 /*!
149 * \internal
150 * \brief Subscribe to D-Bus signals from systemd
151 *
152 * Systemd does not broadcast signal messages unless at least one client has
153 * called the \c Subscribe() method. Also, a D-Bus client ignores broadcast
154 * messages unless an appropriate match rule is set, so we set one here.
155 *
156 * \return Standard Pacemaker return code
157 */
158 static int
159 subscribe_to_signals(void)
160 {
161 const char *match_rule = "type='signal',"
162 "sender='" BUS_NAME "',"
163 "interface='" BUS_NAME_MANAGER "',"
164 "path='" BUS_PATH "'";
165 DBusMessage *reply = NULL;
166 DBusError error;
167
168 /* Tell D-Bus to accept signal messages from systemd.
169 * https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
170 */
171 dbus_error_init(&error);
172 dbus_bus_add_match(systemd_proxy, match_rule, &error);
173
174 if (dbus_error_is_set(&error)) {
175 pcmk__err("Could not listen for systemd DBus signals: %s "
176 QB_XS " (%s)",
177 error.message, error.name);
178 dbus_error_free(&error);
179 return ECOMM;
180 }
181
182 // Tell systemd to broadcast signals
183 reply = systemd_call_simple_method("Subscribe");
184 if (reply == NULL) {
185 dbus_bus_remove_match(systemd_proxy, match_rule, &error);
186 return ECOMM;
187 }
188
189 dbus_message_unref(reply);
190 return pcmk_rc_ok;
191 }
192
193 static bool
194 systemd_init(void)
195 {
196 static int need_init = 1;
197 // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
198
199 if (systemd_proxy
200 && dbus_connection_get_is_connected(systemd_proxy) == FALSE) {
201 pcmk__warn("Connection to System DBus is closed. Reconnecting...");
202 pcmk_dbus_disconnect(systemd_proxy);
203 systemd_proxy = NULL;
204 need_init = 1;
205 }
206
207 if (need_init) {
208 need_init = 0;
209 systemd_proxy = pcmk_dbus_connect();
210
211 if (subscribe_to_signals() != pcmk_rc_ok) {
212 pcmk_dbus_disconnect(systemd_proxy);
213 systemd_proxy = NULL;
214 }
215 }
216
217 return (systemd_proxy != NULL);
218 }
219
220 static inline char *
221 systemd_get_property(const char *unit, const char *name,
222 void (*callback)(const char *name, const char *value, void *userdata),
223 void *userdata, DBusPendingCall **pending, int timeout)
224 {
225 return systemd_proxy?
226 pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT,
227 name, callback, userdata, pending, timeout)
228 : NULL;
229 }
230
231 void
232 systemd_cleanup(void)
233 {
234 if (systemd_proxy) {
235 pcmk_dbus_disconnect(systemd_proxy);
236 systemd_proxy = NULL;
237 }
238 }
239
240 /*
241 * end of systemd_proxy functions
242 */
243
244 /*!
245 * \internal
246 * \brief Check whether a file name represents a manageable systemd unit
247 *
248 * \param[in] name File name to check
249 *
250 * \return Pointer to "dot" before filename extension if so, NULL otherwise
251 */
252 static const char *
253 systemd_unit_extension(const char *name)
254 {
255 if (name) {
256 const char *dot = strrchr(name, '.');
257
258 if (dot && (!strcmp(dot, ".service")
259 || !strcmp(dot, ".socket")
260 || !strcmp(dot, ".mount")
261 || !strcmp(dot, ".timer")
262 || !strcmp(dot, ".path"))) {
263 return dot;
264 }
265 }
266 return NULL;
267 }
268
269 static char *
270 systemd_unit_name(const char *name, bool add_instance_name)
271 {
272 const char *dot = NULL;
273
274 if (pcmk__str_empty(name)) {
275 return NULL;
276 }
277
278 /* Services that end with an @ sign are systemd templates. They expect an
279 * instance name to follow the service name. If no instance name was
280 * provided, just add "pacemaker" to the string as the instance name. It
281 * doesn't seem to matter for purposes of looking up whether a service
282 * exists or not.
283 *
284 * A template can be specified either with or without the unit extension,
285 * so this block handles both cases.
286 */
287 dot = systemd_unit_extension(name);
288
289 if (dot) {
290 if (dot != name && *(dot-1) == '@') {
291 return pcmk__assert_asprintf("%.*spacemaker%s",
292 (int) (dot - name), name, dot);
293 } else {
294 return pcmk__str_copy(name);
295 }
296
297 } else if (add_instance_name && *(name+strlen(name)-1) == '@') {
298 return pcmk__assert_asprintf("%spacemaker.service", name);
299
300 } else {
301 return pcmk__assert_asprintf("%s.service", name);
302 }
303 }
304
305 static void
306 systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data)
307 {
308 DBusError error;
309 DBusMessage *reply = NULL;
310 unsigned int reload_count = GPOINTER_TO_UINT(user_data);
311
312 dbus_error_init(&error);
313 if(pending) {
314 reply = dbus_pending_call_steal_reply(pending);
315 }
316
317 if (pcmk_dbus_find_error(pending, reply, &error)) {
318 pcmk__warn("Could not issue systemd reload %d: %s", reload_count,
319 error.message);
320 dbus_error_free(&error);
321
322 } else {
323 pcmk__trace("Reload %d complete", reload_count);
324 }
325
326 if(pending) {
327 dbus_pending_call_unref(pending);
328 }
329 if(reply) {
330 dbus_message_unref(reply);
331 }
332 }
333
334 static bool
335 systemd_daemon_reload(int timeout)
336 {
337 static unsigned int reload_count = 0;
338 DBusMessage *msg = systemd_new_method("Reload");
339
340 reload_count++;
341 pcmk__assert(msg != NULL);
342 systemd_send(msg, systemd_daemon_reload_complete,
343 GUINT_TO_POINTER(reload_count), timeout);
344 dbus_message_unref(msg);
345
346 return TRUE;
347 }
348
349 /*!
350 * \internal
351 * \brief Set an action result based on a method error
352 *
353 * \param[in,out] op Action to set result for
354 * \param[in] error Method error
355 */
356 static void
357 set_result_from_method_error(svc_action_t *op, const DBusError *error)
358 {
359 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
360 "Unable to invoke systemd DBus method");
361
362 if (dbus_error_has_name(error, "org.freedesktop.systemd1.InvalidName")
363 || dbus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed")
364 || dbus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
365
366 if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
367 pcmk__trace("Masking systemd stop failure (%s) for %s "
368 "because unknown service can be considered stopped",
369 error->name, pcmk__s(op->rsc, "unknown resource"));
370 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
371 return;
372 }
373
374 services__format_result(op, PCMK_OCF_NOT_INSTALLED,
375 PCMK_EXEC_NOT_INSTALLED,
376 "systemd unit %s not found", op->agent);
377
378 /* If systemd happens to be re-executing by `systemctl daemon-reexec` at the
379 * same time, dbus gives an error with the name
380 * `org.freedesktop.DBus.Error.NoReply` and the message "Message recipient
381 * disconnected from message bus without replying".
382 * Consider the monitor pending rather than return an error yet, so that it
383 * can retry with another iteration.
384 */
385 } else if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
386 PCMK_ACTION_STATUS, NULL)
387 && dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)
388 && (strstr(error->message, "disconnected") != NULL)) {
389 services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
390 }
391
392 pcmk__info("DBus request for %s of systemd unit %s%s%s failed: %s",
393 op->action, op->agent,
394 ((op->rsc != NULL)? " for resource " : ""), pcmk__s(op->rsc, ""),
395 error->message);
396 }
397
398 /*!
399 * \internal
400 * \brief Extract unit path from LoadUnit reply, and execute action
401 *
402 * \param[in] reply LoadUnit reply
403 * \param[in,out] op Action to execute (or NULL to just return path)
404 *
405 * \return DBus object path for specified unit if successful (only valid for
406 * lifetime of \p reply), otherwise NULL
407 */
408 static const char *
409 execute_after_loadunit(DBusMessage *reply, svc_action_t *op)
410 {
411 const char *path = NULL;
412 DBusError error;
413
414 /* path here is not used other than as a non-NULL flag to indicate that a
415 * request was indeed sent
416 */
417 if (pcmk_dbus_find_error((void *) &path, reply, &error)) {
418 if (op != NULL) {
419 set_result_from_method_error(op, &error);
420 }
421 dbus_error_free(&error);
422
423 } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
424 __func__, __LINE__)) {
425 if (op != NULL) {
426 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
427 "systemd DBus method had unexpected reply");
428 pcmk__info("Could not load systemd unit %s for %s: DBus reply has "
429 "unexpected type",
430 op->agent, op->id);
431 } else {
432 pcmk__info("Could not load systemd unit: DBus reply has unexpected "
433 "type");
434 }
435
436 } else {
437 dbus_message_get_args (reply, NULL,
438 DBUS_TYPE_OBJECT_PATH, &path,
439 DBUS_TYPE_INVALID);
440 }
441
442 if (op != NULL) {
443 if (path != NULL) {
444 invoke_unit_by_path(op, path);
445
446 } else if (!(op->synchronous)) {
447 if (!pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
448 PCMK_ACTION_STATUS, NULL)
449 || op->status != PCMK_EXEC_PENDING) {
450 services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
451 "No DBus object found for systemd unit %s",
452 op->agent);
453 }
454 services__finalize_async_op(op);
455 }
456 }
457
458 return path;
459 }
460
461 /*!
462 * \internal
463 * \brief Execute a systemd action after its LoadUnit completes
464 *
465 * \param[in,out] pending If not NULL, DBus call associated with LoadUnit
466 * \param[in,out] user_data Action to execute
467 */
468 static void
469 loadunit_completed(DBusPendingCall *pending, void *user_data)
470 {
471 DBusMessage *reply = NULL;
472 svc_action_t *op = user_data;
473
474 pcmk__trace("LoadUnit result for %s arrived", op->id);
475
476 // Grab the reply
477 if (pending != NULL) {
478 reply = dbus_pending_call_steal_reply(pending);
479 }
480
481 // The call is no longer pending
482 CRM_LOG_ASSERT(pending == op->opaque->pending);
483 services_set_op_pending(op, NULL);
484
485 // Execute the desired action based on the reply
486 execute_after_loadunit(reply, user_data);
487 if (reply != NULL) {
488 dbus_message_unref(reply);
489 }
490 }
491
492 /*!
493 * \internal
494 * \brief Execute a systemd action, given the unit name
495 *
496 * \param[in] arg_name Unit name (possibly without ".service" extension)
497 * \param[in,out] op Action to execute (if NULL, just get object path)
498 * \param[out] path If non-NULL and \p op is NULL or synchronous, where
499 * to store DBus object path for specified unit
500 *
501 * \return Standard Pacemaker return code (for NULL \p op, pcmk_rc_ok means unit
502 * was found; for synchronous actions, pcmk_rc_ok means unit was
503 * executed, with the actual result stored in \p op; for asynchronous
504 * actions, pcmk_rc_ok means action was initiated)
505 * \note It is the caller's responsibility to free the path.
506 */
507 static int
508 invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
509 {
510 DBusMessage *msg;
511 DBusMessage *reply = NULL;
512 DBusPendingCall *pending = NULL;
513 char *name = NULL;
514
515 if (pcmk__str_empty(arg_name)) {
516 return EINVAL;
517 }
518
519 if (!systemd_init()) {
520 if (op != NULL) {
521 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
522 "No DBus connection");
523 }
524 return ENOTCONN;
525 }
526
527 /* Create a LoadUnit DBus method (equivalent to GetUnit if already loaded),
528 * which makes the unit usable via further DBus methods.
529 *
530 * <method name="LoadUnit">
531 * <arg name="name" type="s" direction="in"/>
532 * <arg name="unit" type="o" direction="out"/>
533 * </method>
534 */
535 msg = systemd_new_method("LoadUnit");
536 pcmk__assert(msg != NULL);
537
538 // Add the (expanded) unit name as the argument
539 name = systemd_unit_name(arg_name,
540 (op == NULL)
541 || pcmk__str_eq(op->action, PCMK_ACTION_META_DATA,
542 pcmk__str_none));
543 CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
544 DBUS_TYPE_INVALID));
545 free(name);
546
547 if ((op == NULL) || op->synchronous) {
548 // For synchronous ops, wait for a reply and extract the result
549 const char *unit = NULL;
550 int rc = pcmk_rc_ok;
551
552 reply = systemd_send_recv(msg, NULL,
553 (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT));
554 dbus_message_unref(msg);
555
556 unit = execute_after_loadunit(reply, op);
557 if (unit == NULL) {
558 rc = ENOENT;
559 if (path != NULL) {
560 *path = NULL;
561 }
562 } else if (path != NULL) {
563 *path = strdup(unit);
564 if (*path == NULL) {
565 rc = ENOMEM;
566 }
567 }
568
569 if (reply != NULL) {
570 dbus_message_unref(reply);
571 }
572 return rc;
573 }
574
575 // For asynchronous ops, initiate the LoadUnit call and return
576 pending = systemd_send(msg, loadunit_completed, op, op->timeout);
577 if (pending == NULL) {
578 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
579 "Unable to send DBus message");
580 dbus_message_unref(msg);
581 return ECOMM;
582 }
583
584 // LoadUnit was successfully initiated
585 services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
586 services_set_op_pending(op, pending);
587 dbus_message_unref(msg);
588 return pcmk_rc_ok;
589 }
590
591 /*!
592 * \internal
593 * \brief Compare two strings alphabetically (case-insensitive)
594 *
595 * \param[in] a First string to compare
596 * \param[in] b Second string to compare
597 *
598 * \return 0 if strings are equal, -1 if a < b, 1 if a > b
599 *
600 * \note Usable as a GCompareFunc with g_list_sort().
601 * NULL is considered less than non-NULL.
602 */
603 static gint
604 sort_str(gconstpointer a, gconstpointer b)
605 {
606 if (!a && !b) {
607 return 0;
608 } else if (!a) {
609 return -1;
610 } else if (!b) {
611 return 1;
612 }
613 return strcasecmp(a, b);
614 }
615
616 GList *
617 systemd_unit_listall(void)
618 {
619 int nfiles = 0;
620 GList *units = NULL;
621 DBusMessageIter args;
622 DBusMessageIter unit;
623 DBusMessageIter elem;
624 DBusMessage *reply = NULL;
625
626 if (!systemd_init()) {
627 return NULL;
628 }
629
630 /*
631 " <method name=\"ListUnitFiles\">\n" \
632 " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
633 " </method>\n" \
634 */
635
636 reply = systemd_call_simple_method("ListUnitFiles");
637 if (reply == NULL) {
638 return NULL;
639 }
640 if (!dbus_message_iter_init(reply, &args)) {
641 pcmk__err("Could not list systemd unit files: systemd reply has no "
642 "arguments");
643 dbus_message_unref(reply);
644 return NULL;
645 }
646 if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY,
647 __func__, __LINE__)) {
648 pcmk__err("Could not list systemd unit files: systemd reply has "
649 "invalid arguments");
650 dbus_message_unref(reply);
651 return NULL;
652 }
653
654 dbus_message_iter_recurse(&args, &unit);
655 for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID;
656 dbus_message_iter_next(&unit)) {
657
658 DBusBasicValue value;
659 const char *match = NULL;
660 char *unit_name = NULL;
661 char *basename = NULL;
662
663 if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) {
664 pcmk__warn("Skipping systemd reply argument with unexpected type");
665 continue;
666 }
667
668 dbus_message_iter_recurse(&unit, &elem);
669 if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) {
670 pcmk__warn("Skipping systemd reply argument with no string");
671 continue;
672 }
673
674 dbus_message_iter_get_basic(&elem, &value);
675 if (value.str == NULL) {
676 pcmk__debug("ListUnitFiles reply did not provide a string");
677 continue;
678 }
679 pcmk__trace("DBus ListUnitFiles listed: %s", value.str);
680
681 match = systemd_unit_extension(value.str);
682 if (match == NULL) {
683 // This is not a unit file type we know how to manage
684 pcmk__debug("ListUnitFiles entry '%s' is not supported as resource",
685 value.str);
686 continue;
687 }
688
689 // ListUnitFiles returns full path names, we just want base name
690 basename = strrchr(value.str, '/');
691 if (basename) {
692 basename = basename + 1;
693 } else {
694 basename = value.str;
695 }
696
697 if (!strcmp(match, ".service")) {
698 // Service is the "default" unit type, so strip it
699 unit_name = strndup(basename, match - basename);
700 } else {
701 unit_name = strdup(basename);
702 }
703
704 nfiles++;
705 units = g_list_prepend(units, unit_name);
706 }
707
708 dbus_message_unref(reply);
709
710 pcmk__trace("Found %d manageable systemd unit files", nfiles);
711 units = g_list_sort(units, sort_str);
712 return units;
713 }
714
715 bool
716 systemd_unit_exists(const char *name)
717 {
718 char *path = NULL;
719 char *state = NULL;
720 int rc = false;
721
722 /* Note: Makes a blocking dbus calls
723 * Used by resources_find_service_class() when resource class=service
724 */
725 if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
726 || (path == NULL)) {
727 goto done;
728 }
729
730 /* A successful LoadUnit is not sufficient to determine the unit's
731 * existence; it merely means the LoadUnit request received a reply.
732 * We must make another blocking call to check the LoadState property.
733 */
734 state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
735 DBUS_TIMEOUT_USE_DEFAULT);
736 rc = pcmk__str_any_of(state, "loaded", "masked", NULL);
737
738 done:
739 free(path);
740 free(state);
741 return rc;
742 }
743
744 // @TODO Use XML string constants and maybe a real XML object
745 #define METADATA_FORMAT \
746 "<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n" \
747 "<" PCMK_XE_RESOURCE_AGENT " " \
748 PCMK_XA_NAME "=\"%s\" " \
749 PCMK_XA_VERSION "=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n" \
750 " <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n" \
751 " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n" \
752 " %s\n" \
753 " </" PCMK_XE_LONGDESC ">\n" \
754 " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">" \
755 "systemd unit file for %s" \
756 "</" PCMK_XE_SHORTDESC ">\n" \
757 " <" PCMK_XE_PARAMETERS "/>\n" \
758 " <" PCMK_XE_ACTIONS ">\n" \
759 " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_START "\"" \
760 " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
761 " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STOP "\"" \
762 " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
763 " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STATUS "\"" \
764 " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
765 " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_MONITOR "\"" \
766 " " PCMK_META_TIMEOUT "=\"100s\"" \
767 " " PCMK_META_INTERVAL "=\"60s\" />\n" \
768 " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_META_DATA "\"" \
769 " " PCMK_META_TIMEOUT "=\"5s\" />\n" \
770 " </" PCMK_XE_ACTIONS ">\n" \
771 " <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "=\"systemd\"/>\n" \
772 "</" PCMK_XE_RESOURCE_AGENT ">\n"
773
774 static char *
775 systemd_unit_metadata(const char *name, int timeout)
776 {
777 char *meta = NULL;
778 char *desc = NULL;
779 char *path = NULL;
780
781 if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) {
782 /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */
783 desc = systemd_get_property(path, "Description", NULL, NULL, NULL,
784 timeout);
785 } else {
786 desc = pcmk__assert_asprintf("Systemd unit file for %s", name);
787 }
788
789 if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) {
790 gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text);
791
792 meta = pcmk__assert_asprintf(METADATA_FORMAT, name, escaped, name);
793 g_free(escaped);
794
795 } else {
796 meta = pcmk__assert_asprintf(METADATA_FORMAT, name, desc, name);
797 }
798
799 free(desc);
800 free(path);
801 return meta;
802 }
803
804 /*!
805 * \internal
806 * \brief Determine result of method from reply
807 *
808 * \param[in] reply Reply to start, stop, or restart request
809 * \param[in,out] op Action that was executed
810 */
811 static void
812 process_unit_method_reply(DBusMessage *reply, svc_action_t *op)
813 {
814 bool start_stop = pcmk__strcase_any_of(op->action, PCMK_ACTION_START,
815 PCMK_ACTION_STOP, NULL);
816 DBusError error;
817
818 dbus_error_init(&error);
819
820 /* The first use of error here is not used other than as a non-NULL flag to
821 * indicate that a request was indeed sent
822 */
823 if (pcmk_dbus_find_error((void *) &error, reply, &error)) {
824 set_result_from_method_error(op, &error);
825 dbus_error_free(&error);
826
827 } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
828 __func__, __LINE__)) {
829 const char *reason = "systemd D-Bus method had unexpected reply";
830
831 pcmk__info("DBus request for %s of %s succeeded but return type was "
832 "unexpected",
833 op->action, pcmk__s(op->rsc, "unknown resource"));
834
835 if (!op->synchronous && start_stop) {
836 /* The start or stop job is enqueued but is not complete. We need a
837 * job path to detect completion in job_removed_filter().
838 */
839 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
840 reason);
841
842 } else {
843 /* Something weird happened, but the action is finished and there
844 * was no D-Bus error. So call it a success.
845 */
846 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, reason);
847 }
848
849 } else {
850 const char *path = NULL;
851
852 dbus_message_get_args(reply, NULL,
853 DBUS_TYPE_OBJECT_PATH, &path,
854 DBUS_TYPE_INVALID);
855
856 pcmk__debug("DBus request for %s of %s using %s succeeded",
857 op->action, pcmk__s(op->rsc, "unknown resource"), path);
858
859 if (!op->synchronous && start_stop) {
860 // Should be set to unknown/pending already
861 services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
862 pcmk__str_update(&(op->opaque->job_path), path);
863
864 } else {
865 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
866 }
867 }
868 }
869
870 /*!
871 * \internal
872 * \brief Process a systemd \c JobRemoved signal for a given service action
873 *
874 * This filter is expected to be added with \c finalize_async_action_dbus() as
875 * the \c free_data_function. Then if \p message is a \c JobRemoved signal for
876 * the action specified by \p user_data, the action's result is set, the filter
877 * is removed, and the action is finalized.
878 *
879 * \param[in,out] connection D-Bus connection
880 * \param[in] message D-Bus message
881 * \param[in,out] user_data Service action (\c svc_action_t)
882 *
883 * \retval \c DBUS_HANDLER_RESULT_HANDLED if \p message is a \c JobRemoved
884 * signal for \p user_data
885 * \retval \c DBUS_HANDLER_RESULT_NOT_YET_HANDLED otherwise (on error, if
886 * \p message is not a \c JobRemoved signal, or if the signal is for
887 * some other action's job)
888 */
889 static DBusHandlerResult
890 job_removed_filter(DBusConnection *connection, DBusMessage *message,
891 void *user_data)
892 {
893 svc_action_t *action = user_data;
894 const char *action_name = NULL;
895 uint32_t job_id = 0;
896 const char *bus_path = NULL;
897 const char *unit_name = NULL;
898 const char *result = NULL;
899 DBusError error;
900
901 CRM_CHECK((connection != NULL) && (message != NULL),
902 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
903
904 // action should always be set when the filter is added
905 if ((action == NULL)
906 || !dbus_message_is_signal(message, BUS_NAME_MANAGER, "JobRemoved")) {
907 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
908 }
909
910 dbus_error_init(&error);
911 if (!dbus_message_get_args(message, &error,
912 DBUS_TYPE_UINT32, &job_id,
913 DBUS_TYPE_OBJECT_PATH, &bus_path,
914 DBUS_TYPE_STRING, &unit_name,
915 DBUS_TYPE_STRING, &result,
916 DBUS_TYPE_INVALID)) {
917 pcmk__err("Could not interpret systemd DBus signal: %s " QB_XS " (%s)",
918 error.message, error.name);
919 dbus_error_free(&error);
920 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
921 }
922
923 if (!pcmk__str_eq(bus_path, action->opaque->job_path, pcmk__str_none)) {
924 // This filter is not for this job
925 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
926 }
927
928 action_name = pcmk__s(action->action, "(unknown)");
929
930 pcmk__trace("Setting %s result for %s (JobRemoved id=%" PRIu32
931 ", result=%s",
932 action_name, unit_name, job_id, result);
933
934 if (pcmk__str_eq(result, "done", pcmk__str_none)) {
935 services__set_result(action, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
936
937 } else if (pcmk__str_eq(result, "timeout", pcmk__str_none)) {
938 services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
939 "systemd %s job for %s timed out",
940 action_name, unit_name);
941
942 } else {
943 services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
944 "systemd %s job for %s failed with result '%s'",
945 action_name, unit_name, result);
946 }
947
948 /* This instance of the filter was specifically for the given action.
949 *
950 * The action gets finalized by services__finalize_async_op() via the
951 * filter's free_data_function.
952 */
953 dbus_connection_remove_filter(systemd_proxy, job_removed_filter, action);
954 return DBUS_HANDLER_RESULT_HANDLED;
955 }
956
957 /*!
958 * \internal
959 * \brief \c DBusFreeFunction wrapper for \c services__finalize_async_op()
960 *
961 * \param[in,out] action Asynchronous service action to finalize
962 */
963 static void
964 finalize_async_action_dbus(void *action)
965 {
966 services__finalize_async_op((svc_action_t *) action);
967 }
968
969 /*!
970 * \internal
971 * \brief Process the completion of an asynchronous unit start, stop, or restart
972 *
973 * \param[in,out] pending If not NULL, DBus call associated with request
974 * \param[in,out] user_data Action that was executed
975 */
976 static void
977 unit_method_complete(DBusPendingCall *pending, void *user_data)
978 {
979 DBusMessage *reply = NULL;
980 svc_action_t *op = user_data;
981
982 pcmk__trace("Result for %s arrived", op->id);
983
984 // Grab the reply
985 if (pending != NULL) {
986 reply = dbus_pending_call_steal_reply(pending);
987 }
988
989 // The call is no longer pending
990 CRM_LOG_ASSERT(pending == op->opaque->pending);
991 services_set_op_pending(op, NULL);
992
993 process_unit_method_reply(reply, op);
994
995 if (reply != NULL) {
996 dbus_message_unref(reply);
997 }
998
999 if ((op->status == PCMK_EXEC_PENDING)
1000 && pcmk__strcase_any_of(op->action, PCMK_ACTION_START, PCMK_ACTION_STOP,
1001 NULL)) {
1002 /* Start and stop method calls return when the job is enqueued, not when
1003 * it's complete. Start and stop actions must be finalized after the job
1004 * is complete, because the action callback function may use it. We add
1005 * a message filter to process the JobRemoved signal, which indicates
1006 * completion.
1007 *
1008 * The filter takes ownership of op, which will be finalized when the
1009 * filter is later removed.
1010 */
1011 if (dbus_connection_add_filter(systemd_proxy, job_removed_filter, op,
1012 finalize_async_action_dbus)) {
1013 return;
1014 }
1015 pcmk__err("Could not add D-Bus filter for systemd JobRemoved signals");
1016 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1017 "Failed to add D-Bus filter for systemd "
1018 "JobRemoved signal");
1019 }
1020 services__finalize_async_op(op);
1021 }
1022
1023 /* When the cluster manages a systemd resource, we create a unit file override
1024 * to order the service "before" pacemaker. The "before" relationship won't
1025 * actually be used, since systemd won't ever start the resource -- we're
1026 * interested in the reverse shutdown ordering it creates, to ensure that
1027 * systemd doesn't stop the resource at shutdown while pacemaker is still
1028 * running.
1029 *
1030 * @TODO Add start timeout
1031 */
1032 #define SYSTEMD_UNIT_OVERRIDE_TEMPLATE \
1033 "[Unit]\n" \
1034 "Description=Cluster Controlled %s\n" \
1035 "Before=pacemaker.service pacemaker_remote.service\n"
1036
1037 #define SYSTEMD_SERVICE_OVERRIDE \
1038 "\n" \
1039 "[Service]\n" \
1040 "Restart=no\n"
1041
1042 /*!
1043 * \internal
1044 * \brief Get runtime drop-in directory path for a systemd unit
1045 *
1046 * \param[in] unit_name Systemd unit (with extension)
1047 *
1048 * \return Drop-in directory path
1049 */
1050 static GString *
1051 get_override_dir(const char *unit_name)
1052 {
1053 GString *buf = g_string_sized_new(128);
1054
1055 pcmk__g_strcat(buf, "/run/systemd/system/", unit_name, ".d", NULL);
1056 return buf;
1057 }
1058
1059 /*!
1060 * \internal
1061 * \brief Append systemd override filename to a directory path
1062 *
1063 * \param[in,out] buf Buffer containing directory path to append to
1064 */
1065 static inline void
1066 append_override_basename(GString *buf)
1067 {
1068 g_string_append(buf, "/50-pacemaker.conf");
1069 }
1070
1071 /*!
1072 * \internal
1073 * \brief Create a runtime override file for a systemd unit
1074 *
1075 * The systemd daemon is then reloaded. This file does not survive a reboot.
1076 *
1077 * \param[in] agent Systemd resource agent
1078 * \param[in] timeout Timeout for systemd daemon reload
1079 *
1080 * \return Standard Pacemaker return code
1081 *
1082 * \note Any configuration in \c /etc takes precedence over our drop-in.
1083 * \todo Document this in Pacemaker Explained or Administration?
1084 */
1085 static int
1086 systemd_create_override(const char *agent, int timeout)
1087 {
1088 char *unit_name = NULL;
1089 GString *filename = NULL;
1090 GString *override = NULL;
1091 FILE *fp = NULL;
1092 int fd = 0;
1093 int rc = pcmk_rc_ok;
1094
1095 unit_name = systemd_unit_name(agent, false);
1096 CRM_CHECK(!pcmk__str_empty(unit_name),
1097 rc = EINVAL; goto done);
1098
1099 filename = get_override_dir(unit_name);
|
(1) Event deref_ptr: |
Directly dereferencing pointer "filename". |
| Also see events: |
[check_after_deref] |
1100 rc = pcmk__build_path(filename->str, 0755);
1101 if (rc != pcmk_rc_ok) {
1102 pcmk__err("Could not create systemd override directory %s: %s",
1103 filename->str, pcmk_rc_str(rc));
1104 goto done;
1105 }
1106
1107 append_override_basename(filename);
1108 fp = fopen(filename->str, "w");
1109 if (fp == NULL) {
1110 rc = errno;
1111 pcmk__err("Cannot open systemd override file %s for writing: %s",
1112 filename->str, pcmk_rc_str(rc));
1113 goto done;
1114 }
1115
1116 // Ensure the override file is world-readable (avoid systemd warning in log)
1117 fd = fileno(fp);
1118 if ((fd < 0) || (fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)) {
1119 rc = errno;
1120 pcmk__err("Failed to set permissions on systemd override file %s: %s",
1121 filename->str, pcmk_rc_str(rc));
1122 goto done;
1123 }
1124
1125 override = g_string_sized_new(2 * sizeof(SYSTEMD_UNIT_OVERRIDE_TEMPLATE));
1126 g_string_printf(override, SYSTEMD_UNIT_OVERRIDE_TEMPLATE, unit_name);
1127 if (g_str_has_suffix(unit_name, ".service")) {
1128 g_string_append(override, SYSTEMD_SERVICE_OVERRIDE);
1129 }
1130
1131 if (fputs(override->str, fp) == EOF) {
1132 rc = EIO;
1133 pcmk__err("Cannot write to systemd override file %s", filename->str);
1134 }
1135
1136 done:
1137 if (fp != NULL) {
1138 fclose(fp);
1139 }
1140
1141 if (rc == pcmk_rc_ok) {
1142 // @TODO Make sure the reload succeeds
1143 systemd_daemon_reload(timeout);
1144
1145 } else if (fp != NULL) {
1146 // File was created, so remove it
1147 unlink(filename->str);
1148 }
1149
1150 free(unit_name);
1151
|
CID (unavailable; MK=7fa7c3c3b9a593a5e1cc68fe9399dc94) (#1 of 1): Dereference before null check (REVERSE_INULL): |
|
(2) Event check_after_deref: |
Null-checking "filename" suggests that it may be null, but it has already been dereferenced on all paths leading to the check. |
| Also see events: |
[deref_ptr] |
1152 if (filename != NULL) {
1153 g_string_free(filename, TRUE);
1154 }
1155 if (override != NULL) {
1156 g_string_free(override, TRUE);
1157 }
1158 return rc;
1159 }
1160
1161 static void
1162 systemd_remove_override(const char *agent, int timeout)
1163 {
1164 char *unit_name = systemd_unit_name(agent, false);
1165 GString *filename = NULL;
1166
1167 CRM_CHECK(!pcmk__str_empty(unit_name), goto done);
1168
1169 filename = get_override_dir(unit_name);
1170 append_override_basename(filename);
1171
1172 if (unlink(filename->str) < 0) {
1173 int rc = errno;
1174
1175 if (rc != ENOENT) {
1176 // Stop may be called when already stopped, which is fine
1177 pcmk__warn("Cannot remove systemd override file %s: %s",
1178 filename->str, pcmk_rc_str(rc));
1179 }
1180
1181 } else {
1182 systemd_daemon_reload(timeout);
1183 }
1184
1185 done:
1186 free(unit_name);
1187
1188 if (filename != NULL) {
1189 g_string_free(filename, TRUE);
1190 }
1191 }
1192
1193 /*!
1194 * \internal
1195 * \brief Parse result of systemd status check
1196 *
1197 * Set a status action's exit status and execution status based on a DBus
1198 * property check result, and finalize the action if asynchronous.
1199 *
1200 * \param[in] name DBus interface name for property that was checked
1201 * \param[in] state Property value
1202 * \param[in,out] userdata Status action that check was done for
1203 */
1204 static void
1205 parse_status_result(const char *name, const char *state, void *userdata)
1206 {
1207 svc_action_t *op = userdata;
1208
1209 pcmk__trace("Resource %s has %s='%s'", pcmk__s(op->rsc, "(unspecified)"),
1210 name, pcmk__s(state, "<null>"));
1211
1212 if (pcmk__str_eq(state, "active", pcmk__str_none)) {
1213 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1214
1215 } else if (pcmk__str_eq(state, "reloading", pcmk__str_none)) {
1216 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1217
1218 } else if (pcmk__str_eq(state, "activating", pcmk__str_none)) {
1219 services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
1220
1221 } else if (pcmk__str_eq(state, "deactivating", pcmk__str_none)) {
1222 services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
1223
1224 } else {
1225 services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
1226 }
1227
1228 if (!(op->synchronous)) {
1229 services_set_op_pending(op, NULL);
1230 services__finalize_async_op(op);
1231 }
1232 }
1233
1234 /*!
1235 * \internal
1236 * \brief Invoke a systemd unit, given its DBus object path
1237 *
1238 * \param[in,out] op Action to execute
1239 * \param[in] unit DBus object path of systemd unit to invoke
1240 */
1241 static void
1242 invoke_unit_by_path(svc_action_t *op, const char *unit)
1243 {
1244 const char *method = NULL;
1245 DBusMessage *msg = NULL;
1246 DBusMessage *reply = NULL;
1247
1248 if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS,
1249 NULL)) {
1250 DBusPendingCall *pending = NULL;
1251 char *state;
1252
1253 state = systemd_get_property(unit, "ActiveState",
1254 (op->synchronous? NULL : parse_status_result),
1255 op, (op->synchronous? NULL : &pending),
1256 op->timeout);
1257 if (op->synchronous) {
1258 parse_status_result("ActiveState", state, op);
1259 free(state);
1260
1261 } else if (pending == NULL) { // Could not get ActiveState property
1262 services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1263 "Could not get state for unit %s from DBus",
1264 op->agent);
1265 services__finalize_async_op(op);
1266
1267 } else {
1268 services_set_op_pending(op, pending);
1269 }
1270 return;
1271
1272 } else if (pcmk__str_eq(op->action, PCMK_ACTION_START, pcmk__str_none)) {
1273 int rc = pcmk_rc_ok;
1274
1275 method = "StartUnit";
1276 rc = systemd_create_override(op->agent, op->timeout);
1277 if (rc != pcmk_rc_ok) {
1278 services__format_result(op, pcmk_rc2ocf(rc), PCMK_EXEC_ERROR,
1279 "Failed to create systemd override file "
1280 "for %s",
1281 pcmk__s(op->agent, "(unspecified)"));
1282 if (!(op->synchronous)) {
1283 services__finalize_async_op(op);
1284 }
1285 return;
1286 }
1287
1288 } else if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_none)) {
1289 method = "StopUnit";
1290 systemd_remove_override(op->agent, op->timeout);
1291
1292 } else if (pcmk__str_eq(op->action, "restart", pcmk__str_none)) {
1293 method = "RestartUnit";
1294
1295 } else {
1296 services__format_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
1297 PCMK_EXEC_ERROR,
1298 "Action %s not implemented "
1299 "for systemd resources",
1300 pcmk__s(op->action, "(unspecified)"));
1301 if (!(op->synchronous)) {
1302 services__finalize_async_op(op);
1303 }
1304 return;
1305 }
1306
1307 pcmk__trace("Calling %s for unit path %s%s%s", method, unit,
1308 ((op->rsc != NULL)? " for resource " : ""),
1309 pcmk__s(op->rsc, ""));
1310
1311 msg = systemd_new_method(method);
1312 pcmk__assert(msg != NULL);
1313
1314 /* (ss) */
1315 {
1316 const char *replace_s = "replace";
1317 char *name = systemd_unit_name(op->agent,
1318 pcmk__str_eq(op->action,
1319 PCMK_ACTION_META_DATA,
1320 pcmk__str_none));
1321
1322 CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
1323 CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
1324
1325 free(name);
1326 }
1327
1328 if (op->synchronous) {
1329 reply = systemd_send_recv(msg, NULL, op->timeout);
1330 dbus_message_unref(msg);
1331 process_unit_method_reply(reply, op);
1332 if (reply != NULL) {
1333 dbus_message_unref(reply);
1334 }
1335
1336 } else {
1337 DBusPendingCall *pending = systemd_send(msg, unit_method_complete, op,
1338 op->timeout);
1339
1340 dbus_message_unref(msg);
1341 if (pending == NULL) {
1342 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1343 "Unable to send DBus message");
1344 services__finalize_async_op(op);
1345
1346 } else {
1347 services_set_op_pending(op, pending);
1348 }
1349 }
1350 }
1351
1352 static gboolean
1353 systemd_timeout_callback(gpointer p)
1354 {
1355 svc_action_t * op = p;
1356
1357 op->opaque->timerid = 0;
1358 pcmk__info("%s action for systemd unit %s named '%s' timed out", op->action,
1359 op->agent, op->rsc);
1360 services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
1361 "%s action for systemd unit %s "
1362 "did not complete in time", op->action, op->agent);
1363
1364 if (op->opaque->job_path != NULL) {
1365 // A filter owns this op
1366 dbus_connection_remove_filter(systemd_proxy, job_removed_filter, op);
1367
1368 } else {
1369 services__finalize_async_op(op);
1370 }
1371 return FALSE;
1372 }
1373
1374 /*!
1375 * \internal
1376 * \brief Execute a systemd action
1377 *
1378 * \param[in,out] op Action to execute
1379 *
1380 * \return Standard Pacemaker return code
1381 * \retval EBUSY Recurring operation could not be initiated
1382 * \retval pcmk_rc_error Synchronous action failed
1383 * \retval pcmk_rc_ok Synchronous action succeeded, or asynchronous action
1384 * should not be freed (because it's pending or because
1385 * it failed to execute and was already freed)
1386 *
1387 * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
1388 * caller is responsible for freeing the action.
1389 */
1390 int
1391 services__execute_systemd(svc_action_t *op)
1392 {
1393 pcmk__assert(op != NULL);
1394
1395 if (pcmk__str_empty(op->action) || pcmk__str_empty(op->agent)) {
1396 services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
1397 "Bug in action caller");
1398 goto done;
1399 }
1400
1401 if (!systemd_init()) {
1402 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1403 "No DBus connection");
1404 goto done;
1405 }
1406
1407 pcmk__debug("Performing %ssynchronous %s op on systemd unit %s%s%s",
1408 (op->synchronous? "" : "a"), op->action, op->agent,
1409 ((op->rsc != NULL)? " for resource " : ""),
1410 pcmk__s(op->rsc, ""));
1411
1412 if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1413 op->stdout_data = systemd_unit_metadata(op->agent, op->timeout);
1414 services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1415 goto done;
1416 }
1417
1418 /* invoke_unit_by_name() should always override these values, which are here
1419 * just as a fail-safe in case there are any code paths that neglect to
1420 */
1421 services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1422 "Bug in service library");
1423
1424 if (invoke_unit_by_name(op->agent, op, NULL) == pcmk_rc_ok) {
1425 // @TODO Why plus 5000? No explanation in fccd046.
1426 op->opaque->timerid = pcmk__create_timer(op->timeout + 5000,
1427 systemd_timeout_callback, op);
1428 services_add_inflight_op(op);
1429 return pcmk_rc_ok;
1430 }
1431
1432 done:
1433 if (op->synchronous) {
1434 return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
1435 } else {
1436 return services__finalize_async_op(op);
1437 }
1438 }
1439