Line data Source code
1 : /*
2 : * Copyright 2012-2024 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 <sys/stat.h>
18 : #include <gio/gio.h>
19 : #include <services_private.h>
20 : #include <systemd.h>
21 : #include <dbus/dbus.h>
22 : #include <pcmk-dbus.h>
23 :
24 : static void invoke_unit_by_path(svc_action_t *op, const char *unit);
25 :
26 : #define BUS_NAME "org.freedesktop.systemd1"
27 : #define BUS_NAME_MANAGER BUS_NAME ".Manager"
28 : #define BUS_NAME_UNIT BUS_NAME ".Unit"
29 : #define BUS_PATH "/org/freedesktop/systemd1"
30 :
31 : /*!
32 : * \internal
33 : * \brief Prepare a systemd action
34 : *
35 : * \param[in,out] op Action to prepare
36 : *
37 : * \return Standard Pacemaker return code
38 : */
39 : int
40 0 : services__systemd_prepare(svc_action_t *op)
41 : {
42 0 : op->opaque->exec = strdup("systemd-dbus");
43 0 : if (op->opaque->exec == NULL) {
44 0 : return ENOMEM;
45 : }
46 0 : return pcmk_rc_ok;
47 : }
48 :
49 : /*!
50 : * \internal
51 : * \brief Map a systemd result to a standard OCF result
52 : *
53 : * \param[in] exit_status Systemd result
54 : *
55 : * \return Standard OCF result
56 : */
57 : enum ocf_exitcode
58 0 : services__systemd2ocf(int exit_status)
59 : {
60 : // This library uses OCF codes for systemd actions
61 0 : return (enum ocf_exitcode) exit_status;
62 : }
63 :
64 : static inline DBusMessage *
65 0 : systemd_new_method(const char *method)
66 : {
67 0 : crm_trace("Calling: %s on " BUS_NAME_MANAGER, method);
68 0 : return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER,
69 : method);
70 : }
71 :
72 : /*
73 : * Functions to manage a static DBus connection
74 : */
75 :
76 : static DBusConnection* systemd_proxy = NULL;
77 :
78 : static inline DBusPendingCall *
79 0 : systemd_send(DBusMessage *msg,
80 : void(*done)(DBusPendingCall *pending, void *user_data),
81 : void *user_data, int timeout)
82 : {
83 0 : return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout);
84 : }
85 :
86 : static inline DBusMessage *
87 0 : systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout)
88 : {
89 0 : return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout);
90 : }
91 :
92 : /*!
93 : * \internal
94 : * \brief Send a method to systemd without arguments, and wait for reply
95 : *
96 : * \param[in] method Method to send
97 : *
98 : * \return Systemd reply on success, NULL (and error will be logged) otherwise
99 : *
100 : * \note The caller must call dbus_message_unref() on the reply after
101 : * handling it.
102 : */
103 : static DBusMessage *
104 0 : systemd_call_simple_method(const char *method)
105 : {
106 0 : DBusMessage *msg = systemd_new_method(method);
107 0 : DBusMessage *reply = NULL;
108 : DBusError error;
109 :
110 : /* Don't call systemd_init() here, because that calls this */
111 0 : CRM_CHECK(systemd_proxy, return NULL);
112 :
113 0 : if (msg == NULL) {
114 0 : crm_err("Could not create message to send %s to systemd", method);
115 0 : return NULL;
116 : }
117 :
118 0 : dbus_error_init(&error);
119 0 : reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT);
120 0 : dbus_message_unref(msg);
121 :
122 0 : if (dbus_error_is_set(&error)) {
123 0 : crm_err("Could not send %s to systemd: %s (%s)",
124 : method, error.message, error.name);
125 0 : dbus_error_free(&error);
126 0 : return NULL;
127 :
128 0 : } else if (reply == NULL) {
129 0 : crm_err("Could not send %s to systemd: no reply received", method);
130 0 : return NULL;
131 : }
132 :
133 0 : return reply;
134 : }
135 :
136 : static gboolean
137 0 : systemd_init(void)
138 : {
139 : static int need_init = 1;
140 : // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
141 :
142 0 : if (systemd_proxy
143 0 : && dbus_connection_get_is_connected(systemd_proxy) == FALSE) {
144 0 : crm_warn("Connection to System DBus is closed. Reconnecting...");
145 0 : pcmk_dbus_disconnect(systemd_proxy);
146 0 : systemd_proxy = NULL;
147 0 : need_init = 1;
148 : }
149 :
150 0 : if (need_init) {
151 0 : need_init = 0;
152 0 : systemd_proxy = pcmk_dbus_connect();
153 : }
154 0 : if (systemd_proxy == NULL) {
155 0 : return FALSE;
156 : }
157 0 : return TRUE;
158 : }
159 :
160 : static inline char *
161 0 : systemd_get_property(const char *unit, const char *name,
162 : void (*callback)(const char *name, const char *value, void *userdata),
163 : void *userdata, DBusPendingCall **pending, int timeout)
164 : {
165 0 : return systemd_proxy?
166 0 : pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT,
167 : name, callback, userdata, pending, timeout)
168 0 : : NULL;
169 : }
170 :
171 : void
172 0 : systemd_cleanup(void)
173 : {
174 0 : if (systemd_proxy) {
175 0 : pcmk_dbus_disconnect(systemd_proxy);
176 0 : systemd_proxy = NULL;
177 : }
178 0 : }
179 :
180 : /*
181 : * end of systemd_proxy functions
182 : */
183 :
184 : /*!
185 : * \internal
186 : * \brief Check whether a file name represents a manageable systemd unit
187 : *
188 : * \param[in] name File name to check
189 : *
190 : * \return Pointer to "dot" before filename extension if so, NULL otherwise
191 : */
192 : static const char *
193 0 : systemd_unit_extension(const char *name)
194 : {
195 0 : if (name) {
196 0 : const char *dot = strrchr(name, '.');
197 :
198 0 : if (dot && (!strcmp(dot, ".service")
199 0 : || !strcmp(dot, ".socket")
200 0 : || !strcmp(dot, ".mount")
201 0 : || !strcmp(dot, ".timer")
202 0 : || !strcmp(dot, ".path"))) {
203 0 : return dot;
204 : }
205 : }
206 0 : return NULL;
207 : }
208 :
209 : static char *
210 0 : systemd_service_name(const char *name, bool add_instance_name)
211 : {
212 0 : const char *dot = NULL;
213 :
214 0 : if (pcmk__str_empty(name)) {
215 0 : return NULL;
216 : }
217 :
218 : /* Services that end with an @ sign are systemd templates. They expect an
219 : * instance name to follow the service name. If no instance name was
220 : * provided, just add "pacemaker" to the string as the instance name. It
221 : * doesn't seem to matter for purposes of looking up whether a service
222 : * exists or not.
223 : *
224 : * A template can be specified either with or without the unit extension,
225 : * so this block handles both cases.
226 : */
227 0 : dot = systemd_unit_extension(name);
228 :
229 0 : if (dot) {
230 0 : if (dot != name && *(dot-1) == '@') {
231 0 : char *s = NULL;
232 :
233 0 : if (asprintf(&s, "%.*spacemaker%s", (int) (dot-name), name, dot) == -1) {
234 : /* If asprintf fails, just return name. */
235 0 : return strdup(name);
236 : }
237 :
238 0 : return s;
239 : } else {
240 0 : return strdup(name);
241 : }
242 :
243 0 : } else if (add_instance_name && *(name+strlen(name)-1) == '@') {
244 0 : return crm_strdup_printf("%spacemaker.service", name);
245 :
246 : } else {
247 0 : return crm_strdup_printf("%s.service", name);
248 : }
249 : }
250 :
251 : static void
252 0 : systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data)
253 : {
254 : DBusError error;
255 0 : DBusMessage *reply = NULL;
256 0 : unsigned int reload_count = GPOINTER_TO_UINT(user_data);
257 :
258 0 : dbus_error_init(&error);
259 0 : if(pending) {
260 0 : reply = dbus_pending_call_steal_reply(pending);
261 : }
262 :
263 0 : if (pcmk_dbus_find_error(pending, reply, &error)) {
264 0 : crm_warn("Could not issue systemd reload %d: %s",
265 : reload_count, error.message);
266 0 : dbus_error_free(&error);
267 :
268 : } else {
269 0 : crm_trace("Reload %d complete", reload_count);
270 : }
271 :
272 0 : if(pending) {
273 0 : dbus_pending_call_unref(pending);
274 : }
275 0 : if(reply) {
276 0 : dbus_message_unref(reply);
277 : }
278 0 : }
279 :
280 : static bool
281 0 : systemd_daemon_reload(int timeout)
282 : {
283 : static unsigned int reload_count = 0;
284 0 : DBusMessage *msg = systemd_new_method("Reload");
285 :
286 0 : reload_count++;
287 0 : CRM_ASSERT(msg != NULL);
288 0 : systemd_send(msg, systemd_daemon_reload_complete,
289 0 : GUINT_TO_POINTER(reload_count), timeout);
290 0 : dbus_message_unref(msg);
291 :
292 0 : return TRUE;
293 : }
294 :
295 : /*!
296 : * \internal
297 : * \brief Set an action result based on a method error
298 : *
299 : * \param[in,out] op Action to set result for
300 : * \param[in] error Method error
301 : */
302 : static void
303 0 : set_result_from_method_error(svc_action_t *op, const DBusError *error)
304 : {
305 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
306 : "Unable to invoke systemd DBus method");
307 :
308 0 : if (strstr(error->name, "org.freedesktop.systemd1.InvalidName")
309 0 : || strstr(error->name, "org.freedesktop.systemd1.LoadFailed")
310 0 : || strstr(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
311 :
312 0 : if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
313 0 : crm_trace("Masking systemd stop failure (%s) for %s "
314 : "because unknown service can be considered stopped",
315 : error->name, pcmk__s(op->rsc, "unknown resource"));
316 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
317 0 : return;
318 : }
319 :
320 0 : services__format_result(op, PCMK_OCF_NOT_INSTALLED,
321 : PCMK_EXEC_NOT_INSTALLED,
322 : "systemd unit %s not found", op->agent);
323 : }
324 :
325 0 : crm_info("DBus request for %s of systemd unit %s%s%s failed: %s",
326 : op->action, op->agent,
327 : ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""),
328 : error->message);
329 : }
330 :
331 : /*!
332 : * \internal
333 : * \brief Extract unit path from LoadUnit reply, and execute action
334 : *
335 : * \param[in] reply LoadUnit reply
336 : * \param[in,out] op Action to execute (or NULL to just return path)
337 : *
338 : * \return DBus object path for specified unit if successful (only valid for
339 : * lifetime of \p reply), otherwise NULL
340 : */
341 : static const char *
342 0 : execute_after_loadunit(DBusMessage *reply, svc_action_t *op)
343 : {
344 0 : const char *path = NULL;
345 : DBusError error;
346 :
347 : /* path here is not used other than as a non-NULL flag to indicate that a
348 : * request was indeed sent
349 : */
350 0 : if (pcmk_dbus_find_error((void *) &path, reply, &error)) {
351 0 : if (op != NULL) {
352 0 : set_result_from_method_error(op, &error);
353 : }
354 0 : dbus_error_free(&error);
355 :
356 0 : } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
357 : __func__, __LINE__)) {
358 0 : if (op != NULL) {
359 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
360 : "systemd DBus method had unexpected reply");
361 0 : crm_info("Could not load systemd unit %s for %s: "
362 : "DBus reply has unexpected type", op->agent, op->id);
363 : } else {
364 0 : crm_info("Could not load systemd unit: "
365 : "DBus reply has unexpected type");
366 : }
367 :
368 : } else {
369 0 : dbus_message_get_args (reply, NULL,
370 : DBUS_TYPE_OBJECT_PATH, &path,
371 : DBUS_TYPE_INVALID);
372 : }
373 :
374 0 : if (op != NULL) {
375 0 : if (path != NULL) {
376 0 : invoke_unit_by_path(op, path);
377 :
378 0 : } else if (!(op->synchronous)) {
379 0 : services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
380 : "No DBus object found for systemd unit %s",
381 : op->agent);
382 0 : services__finalize_async_op(op);
383 : }
384 : }
385 :
386 0 : return path;
387 : }
388 :
389 : /*!
390 : * \internal
391 : * \brief Execute a systemd action after its LoadUnit completes
392 : *
393 : * \param[in,out] pending If not NULL, DBus call associated with LoadUnit
394 : * \param[in,out] user_data Action to execute
395 : */
396 : static void
397 0 : loadunit_completed(DBusPendingCall *pending, void *user_data)
398 : {
399 0 : DBusMessage *reply = NULL;
400 0 : svc_action_t *op = user_data;
401 :
402 0 : crm_trace("LoadUnit result for %s arrived", op->id);
403 :
404 : // Grab the reply
405 0 : if (pending != NULL) {
406 0 : reply = dbus_pending_call_steal_reply(pending);
407 : }
408 :
409 : // The call is no longer pending
410 0 : CRM_LOG_ASSERT(pending == op->opaque->pending);
411 0 : services_set_op_pending(op, NULL);
412 :
413 : // Execute the desired action based on the reply
414 0 : execute_after_loadunit(reply, user_data);
415 0 : if (reply != NULL) {
416 0 : dbus_message_unref(reply);
417 : }
418 0 : }
419 :
420 : /*!
421 : * \internal
422 : * \brief Execute a systemd action, given the unit name
423 : *
424 : * \param[in] arg_name Unit name (possibly without ".service" extension)
425 : * \param[in,out] op Action to execute (if NULL, just get object path)
426 : * \param[out] path If non-NULL and \p op is NULL or synchronous, where
427 : * to store DBus object path for specified unit
428 : *
429 : * \return Standard Pacemaker return code (for NULL \p op, pcmk_rc_ok means unit
430 : * was found; for synchronous actions, pcmk_rc_ok means unit was
431 : * executed, with the actual result stored in \p op; for asynchronous
432 : * actions, pcmk_rc_ok means action was initiated)
433 : * \note It is the caller's responsibility to free the path.
434 : */
435 : static int
436 0 : invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
437 : {
438 : DBusMessage *msg;
439 0 : DBusMessage *reply = NULL;
440 0 : DBusPendingCall *pending = NULL;
441 0 : char *name = NULL;
442 :
443 0 : if (!systemd_init()) {
444 0 : if (op != NULL) {
445 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
446 : "No DBus connection");
447 : }
448 0 : return ENOTCONN;
449 : }
450 :
451 : /* Create a LoadUnit DBus method (equivalent to GetUnit if already loaded),
452 : * which makes the unit usable via further DBus methods.
453 : *
454 : * <method name="LoadUnit">
455 : * <arg name="name" type="s" direction="in"/>
456 : * <arg name="unit" type="o" direction="out"/>
457 : * </method>
458 : */
459 0 : msg = systemd_new_method("LoadUnit");
460 0 : CRM_ASSERT(msg != NULL);
461 :
462 : // Add the (expanded) unit name as the argument
463 0 : name = systemd_service_name(arg_name,
464 : (op == NULL)
465 0 : || pcmk__str_eq(op->action,
466 : PCMK_ACTION_META_DATA,
467 : pcmk__str_none));
468 0 : CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
469 : DBUS_TYPE_INVALID));
470 0 : free(name);
471 :
472 0 : if ((op == NULL) || op->synchronous) {
473 : // For synchronous ops, wait for a reply and extract the result
474 0 : const char *unit = NULL;
475 0 : int rc = pcmk_rc_ok;
476 :
477 0 : reply = systemd_send_recv(msg, NULL,
478 : (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT));
479 0 : dbus_message_unref(msg);
480 :
481 0 : unit = execute_after_loadunit(reply, op);
482 0 : if (unit == NULL) {
483 0 : rc = ENOENT;
484 0 : if (path != NULL) {
485 0 : *path = NULL;
486 : }
487 0 : } else if (path != NULL) {
488 0 : *path = strdup(unit);
489 0 : if (*path == NULL) {
490 0 : rc = ENOMEM;
491 : }
492 : }
493 :
494 0 : if (reply != NULL) {
495 0 : dbus_message_unref(reply);
496 : }
497 0 : return rc;
498 : }
499 :
500 : // For asynchronous ops, initiate the LoadUnit call and return
501 0 : pending = systemd_send(msg, loadunit_completed, op, op->timeout);
502 0 : if (pending == NULL) {
503 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
504 : "Unable to send DBus message");
505 0 : dbus_message_unref(msg);
506 0 : return ECOMM;
507 : }
508 :
509 : // LoadUnit was successfully initiated
510 0 : services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
511 0 : services_set_op_pending(op, pending);
512 0 : dbus_message_unref(msg);
513 0 : return pcmk_rc_ok;
514 : }
515 :
516 : /*!
517 : * \internal
518 : * \brief Compare two strings alphabetically (case-insensitive)
519 : *
520 : * \param[in] a First string to compare
521 : * \param[in] b Second string to compare
522 : *
523 : * \return 0 if strings are equal, -1 if a < b, 1 if a > b
524 : *
525 : * \note Usable as a GCompareFunc with g_list_sort().
526 : * NULL is considered less than non-NULL.
527 : */
528 : static gint
529 0 : sort_str(gconstpointer a, gconstpointer b)
530 : {
531 0 : if (!a && !b) {
532 0 : return 0;
533 0 : } else if (!a) {
534 0 : return -1;
535 0 : } else if (!b) {
536 0 : return 1;
537 : }
538 0 : return strcasecmp(a, b);
539 : }
540 :
541 : GList *
542 0 : systemd_unit_listall(void)
543 : {
544 0 : int nfiles = 0;
545 0 : GList *units = NULL;
546 : DBusMessageIter args;
547 : DBusMessageIter unit;
548 : DBusMessageIter elem;
549 0 : DBusMessage *reply = NULL;
550 :
551 0 : if (systemd_init() == FALSE) {
552 0 : return NULL;
553 : }
554 :
555 : /*
556 : " <method name=\"ListUnitFiles\">\n" \
557 : " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
558 : " </method>\n" \
559 : */
560 :
561 0 : reply = systemd_call_simple_method("ListUnitFiles");
562 0 : if (reply == NULL) {
563 0 : return NULL;
564 : }
565 0 : if (!dbus_message_iter_init(reply, &args)) {
566 0 : crm_err("Could not list systemd unit files: systemd reply has no arguments");
567 0 : dbus_message_unref(reply);
568 0 : return NULL;
569 : }
570 0 : if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY,
571 : __func__, __LINE__)) {
572 0 : crm_err("Could not list systemd unit files: systemd reply has invalid arguments");
573 0 : dbus_message_unref(reply);
574 0 : return NULL;
575 : }
576 :
577 0 : dbus_message_iter_recurse(&args, &unit);
578 0 : for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID;
579 0 : dbus_message_iter_next(&unit)) {
580 :
581 : DBusBasicValue value;
582 0 : const char *match = NULL;
583 0 : char *unit_name = NULL;
584 0 : char *basename = NULL;
585 :
586 0 : if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) {
587 0 : crm_warn("Skipping systemd reply argument with unexpected type");
588 0 : continue;
589 : }
590 :
591 0 : dbus_message_iter_recurse(&unit, &elem);
592 0 : if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) {
593 0 : crm_warn("Skipping systemd reply argument with no string");
594 0 : continue;
595 : }
596 :
597 0 : dbus_message_iter_get_basic(&elem, &value);
598 0 : if (value.str == NULL) {
599 0 : crm_debug("ListUnitFiles reply did not provide a string");
600 0 : continue;
601 : }
602 0 : crm_trace("DBus ListUnitFiles listed: %s", value.str);
603 :
604 0 : match = systemd_unit_extension(value.str);
605 0 : if (match == NULL) {
606 : // This is not a unit file type we know how to manage
607 0 : crm_debug("ListUnitFiles entry '%s' is not supported as resource",
608 : value.str);
609 0 : continue;
610 : }
611 :
612 : // ListUnitFiles returns full path names, we just want base name
613 0 : basename = strrchr(value.str, '/');
614 0 : if (basename) {
615 0 : basename = basename + 1;
616 : } else {
617 0 : basename = value.str;
618 : }
619 :
620 0 : if (!strcmp(match, ".service")) {
621 : // Service is the "default" unit type, so strip it
622 0 : unit_name = strndup(basename, match - basename);
623 : } else {
624 0 : unit_name = strdup(basename);
625 : }
626 :
627 0 : nfiles++;
628 0 : units = g_list_prepend(units, unit_name);
629 : }
630 :
631 0 : dbus_message_unref(reply);
632 :
633 0 : crm_trace("Found %d manageable systemd unit files", nfiles);
634 0 : units = g_list_sort(units, sort_str);
635 0 : return units;
636 : }
637 :
638 : gboolean
639 0 : systemd_unit_exists(const char *name)
640 : {
641 0 : char *path = NULL;
642 0 : char *state = NULL;
643 :
644 : /* Note: Makes a blocking dbus calls
645 : * Used by resources_find_service_class() when resource class=service
646 : */
647 0 : if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
648 0 : || (path == NULL)) {
649 0 : return FALSE;
650 : }
651 :
652 : /* A successful LoadUnit is not sufficient to determine the unit's
653 : * existence; it merely means the LoadUnit request received a reply.
654 : * We must make another blocking call to check the LoadState property.
655 : */
656 0 : state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
657 : DBUS_TIMEOUT_USE_DEFAULT);
658 0 : free(path);
659 0 : if (pcmk__str_any_of(state, "loaded", "masked", NULL)) {
660 0 : free(state);
661 0 : return TRUE;
662 : }
663 0 : free(state);
664 0 : return FALSE;
665 : }
666 :
667 : // @TODO Use XML string constants and maybe a real XML object
668 : #define METADATA_FORMAT \
669 : "<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n" \
670 : "<" PCMK_XE_RESOURCE_AGENT " " \
671 : PCMK_XA_NAME "=\"%s\" " \
672 : PCMK_XA_VERSION "=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n" \
673 : " <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n" \
674 : " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n" \
675 : " %s\n" \
676 : " </" PCMK_XE_LONGDESC ">\n" \
677 : " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">" \
678 : "systemd unit file for %s" \
679 : "</" PCMK_XE_SHORTDESC ">\n" \
680 : " <" PCMK_XE_PARAMETERS "/>\n" \
681 : " <" PCMK_XE_ACTIONS ">\n" \
682 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_START "\"" \
683 : " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
684 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STOP "\"" \
685 : " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
686 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STATUS "\"" \
687 : " " PCMK_META_TIMEOUT "=\"100s\" />\n" \
688 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_MONITOR "\"" \
689 : " " PCMK_META_TIMEOUT "=\"100s\"" \
690 : " " PCMK_META_INTERVAL "=\"60s\" />\n" \
691 : " <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_META_DATA "\"" \
692 : " " PCMK_META_TIMEOUT "=\"5s\" />\n" \
693 : " </" PCMK_XE_ACTIONS ">\n" \
694 : " <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "=\"systemd\"/>\n" \
695 : "</" PCMK_XE_RESOURCE_AGENT ">\n"
696 :
697 : static char *
698 0 : systemd_unit_metadata(const char *name, int timeout)
699 : {
700 0 : char *meta = NULL;
701 0 : char *desc = NULL;
702 0 : char *path = NULL;
703 :
704 0 : if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) {
705 : /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */
706 0 : desc = systemd_get_property(path, "Description", NULL, NULL, NULL,
707 : timeout);
708 : } else {
709 0 : desc = crm_strdup_printf("Systemd unit file for %s", name);
710 : }
711 :
712 0 : if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) {
713 0 : gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text);
714 :
715 0 : meta = crm_strdup_printf(METADATA_FORMAT, name, escaped, name);
716 0 : g_free(escaped);
717 :
718 : } else {
719 0 : meta = crm_strdup_printf(METADATA_FORMAT, name, desc, name);
720 : }
721 :
722 0 : free(desc);
723 0 : free(path);
724 0 : return meta;
725 : }
726 :
727 : /*!
728 : * \internal
729 : * \brief Determine result of method from reply
730 : *
731 : * \param[in] reply Reply to start, stop, or restart request
732 : * \param[in,out] op Action that was executed
733 : */
734 : static void
735 0 : process_unit_method_reply(DBusMessage *reply, svc_action_t *op)
736 : {
737 : DBusError error;
738 :
739 0 : dbus_error_init(&error);
740 :
741 : /* The first use of error here is not used other than as a non-NULL flag to
742 : * indicate that a request was indeed sent
743 : */
744 0 : if (pcmk_dbus_find_error((void *) &error, reply, &error)) {
745 0 : set_result_from_method_error(op, &error);
746 0 : dbus_error_free(&error);
747 :
748 0 : } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
749 : __func__, __LINE__)) {
750 0 : crm_info("DBus request for %s of %s succeeded but "
751 : "return type was unexpected",
752 : op->action, pcmk__s(op->rsc, "unknown resource"));
753 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE,
754 : "systemd DBus method had unexpected reply");
755 :
756 : } else {
757 0 : const char *path = NULL;
758 :
759 0 : dbus_message_get_args(reply, NULL,
760 : DBUS_TYPE_OBJECT_PATH, &path,
761 : DBUS_TYPE_INVALID);
762 0 : crm_debug("DBus request for %s of %s using %s succeeded",
763 : op->action, pcmk__s(op->rsc, "unknown resource"), path);
764 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
765 : }
766 0 : }
767 :
768 : /*!
769 : * \internal
770 : * \brief Process the completion of an asynchronous unit start, stop, or restart
771 : *
772 : * \param[in,out] pending If not NULL, DBus call associated with request
773 : * \param[in,out] user_data Action that was executed
774 : */
775 : static void
776 0 : unit_method_complete(DBusPendingCall *pending, void *user_data)
777 : {
778 0 : DBusMessage *reply = NULL;
779 0 : svc_action_t *op = user_data;
780 :
781 0 : crm_trace("Result for %s arrived", op->id);
782 :
783 : // Grab the reply
784 0 : if (pending != NULL) {
785 0 : reply = dbus_pending_call_steal_reply(pending);
786 : }
787 :
788 : // The call is no longer pending
789 0 : CRM_LOG_ASSERT(pending == op->opaque->pending);
790 0 : services_set_op_pending(op, NULL);
791 :
792 : // Determine result and finalize action
793 0 : process_unit_method_reply(reply, op);
794 0 : services__finalize_async_op(op);
795 0 : if (reply != NULL) {
796 0 : dbus_message_unref(reply);
797 : }
798 0 : }
799 :
800 : #define SYSTEMD_OVERRIDE_ROOT "/run/systemd/system/"
801 :
802 : /* When the cluster manages a systemd resource, we create a unit file override
803 : * to order the service "before" pacemaker. The "before" relationship won't
804 : * actually be used, since systemd won't ever start the resource -- we're
805 : * interested in the reverse shutdown ordering it creates, to ensure that
806 : * systemd doesn't stop the resource at shutdown while pacemaker is still
807 : * running.
808 : *
809 : * @TODO Add start timeout
810 : */
811 : #define SYSTEMD_OVERRIDE_TEMPLATE \
812 : "[Unit]\n" \
813 : "Description=Cluster Controlled %s\n" \
814 : "Before=pacemaker.service pacemaker_remote.service\n" \
815 : "\n" \
816 : "[Service]\n" \
817 : "Restart=no\n"
818 :
819 : // Temporarily use rwxr-xr-x umask when opening a file for writing
820 : static FILE *
821 0 : create_world_readable(const char *filename)
822 : {
823 0 : mode_t orig_umask = umask(S_IWGRP | S_IWOTH);
824 0 : FILE *fp = fopen(filename, "w");
825 :
826 0 : umask(orig_umask);
827 0 : return fp;
828 : }
829 :
830 : static void
831 0 : create_override_dir(const char *agent)
832 : {
833 0 : char *override_dir = crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
834 : "/%s.service.d", agent);
835 0 : int rc = pcmk__build_path(override_dir, 0755);
836 :
837 0 : if (rc != pcmk_rc_ok) {
838 0 : crm_warn("Could not create systemd override directory %s: %s",
839 : override_dir, pcmk_rc_str(rc));
840 : }
841 0 : free(override_dir);
842 0 : }
843 :
844 : static char *
845 0 : get_override_filename(const char *agent)
846 : {
847 0 : return crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
848 : "/%s.service.d/50-pacemaker.conf", agent);
849 : }
850 :
851 : static void
852 0 : systemd_create_override(const char *agent, int timeout)
853 : {
854 0 : FILE *file_strm = NULL;
855 0 : char *override_file = get_override_filename(agent);
856 :
857 0 : create_override_dir(agent);
858 :
859 : /* Ensure the override file is world-readable. This is not strictly
860 : * necessary, but it avoids a systemd warning in the logs.
861 : */
862 0 : file_strm = create_world_readable(override_file);
863 0 : if (file_strm == NULL) {
864 0 : crm_err("Cannot open systemd override file %s for writing",
865 : override_file);
866 : } else {
867 0 : char *override = crm_strdup_printf(SYSTEMD_OVERRIDE_TEMPLATE, agent);
868 :
869 0 : int rc = fprintf(file_strm, "%s\n", override);
870 :
871 0 : free(override);
872 0 : if (rc < 0) {
873 0 : crm_perror(LOG_WARNING, "Cannot write to systemd override file %s",
874 : override_file);
875 : }
876 0 : fflush(file_strm);
877 0 : fclose(file_strm);
878 0 : systemd_daemon_reload(timeout);
879 : }
880 :
881 0 : free(override_file);
882 0 : }
883 :
884 : static void
885 0 : systemd_remove_override(const char *agent, int timeout)
886 : {
887 0 : char *override_file = get_override_filename(agent);
888 0 : int rc = unlink(override_file);
889 :
890 0 : if (rc < 0) {
891 : // Stop may be called when already stopped, which is fine
892 0 : crm_perror(LOG_DEBUG, "Cannot remove systemd override file %s",
893 : override_file);
894 : } else {
895 0 : systemd_daemon_reload(timeout);
896 : }
897 0 : free(override_file);
898 0 : }
899 :
900 : /*!
901 : * \internal
902 : * \brief Parse result of systemd status check
903 : *
904 : * Set a status action's exit status and execution status based on a DBus
905 : * property check result, and finalize the action if asynchronous.
906 : *
907 : * \param[in] name DBus interface name for property that was checked
908 : * \param[in] state Property value
909 : * \param[in,out] userdata Status action that check was done for
910 : */
911 : static void
912 0 : parse_status_result(const char *name, const char *state, void *userdata)
913 : {
914 0 : svc_action_t *op = userdata;
915 :
916 0 : crm_trace("Resource %s has %s='%s'",
917 : pcmk__s(op->rsc, "(unspecified)"), name,
918 : pcmk__s(state, "<null>"));
919 :
920 0 : if (pcmk__str_eq(state, "active", pcmk__str_none)) {
921 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
922 :
923 0 : } else if (pcmk__str_eq(state, "reloading", pcmk__str_none)) {
924 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
925 :
926 0 : } else if (pcmk__str_eq(state, "activating", pcmk__str_none)) {
927 0 : services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
928 :
929 0 : } else if (pcmk__str_eq(state, "deactivating", pcmk__str_none)) {
930 0 : services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
931 :
932 : } else {
933 0 : services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
934 : }
935 :
936 0 : if (!(op->synchronous)) {
937 0 : services_set_op_pending(op, NULL);
938 0 : services__finalize_async_op(op);
939 : }
940 0 : }
941 :
942 : /*!
943 : * \internal
944 : * \brief Invoke a systemd unit, given its DBus object path
945 : *
946 : * \param[in,out] op Action to execute
947 : * \param[in] unit DBus object path of systemd unit to invoke
948 : */
949 : static void
950 0 : invoke_unit_by_path(svc_action_t *op, const char *unit)
951 : {
952 0 : const char *method = NULL;
953 0 : DBusMessage *msg = NULL;
954 0 : DBusMessage *reply = NULL;
955 :
956 0 : if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS,
957 : NULL)) {
958 0 : DBusPendingCall *pending = NULL;
959 : char *state;
960 :
961 0 : state = systemd_get_property(unit, "ActiveState",
962 0 : (op->synchronous? NULL : parse_status_result),
963 0 : op, (op->synchronous? NULL : &pending),
964 : op->timeout);
965 0 : if (op->synchronous) {
966 0 : parse_status_result("ActiveState", state, op);
967 0 : free(state);
968 :
969 0 : } else if (pending == NULL) { // Could not get ActiveState property
970 0 : services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
971 : "Could not get state for unit %s from DBus",
972 : op->agent);
973 0 : services__finalize_async_op(op);
974 :
975 : } else {
976 0 : services_set_op_pending(op, pending);
977 : }
978 0 : return;
979 :
980 0 : } else if (pcmk__str_eq(op->action, PCMK_ACTION_START, pcmk__str_none)) {
981 0 : method = "StartUnit";
982 0 : systemd_create_override(op->agent, op->timeout);
983 :
984 0 : } else if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_none)) {
985 0 : method = "StopUnit";
986 0 : systemd_remove_override(op->agent, op->timeout);
987 :
988 0 : } else if (pcmk__str_eq(op->action, "restart", pcmk__str_none)) {
989 0 : method = "RestartUnit";
990 :
991 : } else {
992 0 : services__format_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
993 : PCMK_EXEC_ERROR,
994 : "Action %s not implemented "
995 : "for systemd resources",
996 0 : pcmk__s(op->action, "(unspecified)"));
997 0 : if (!(op->synchronous)) {
998 0 : services__finalize_async_op(op);
999 : }
1000 0 : return;
1001 : }
1002 :
1003 0 : crm_trace("Calling %s for unit path %s%s%s",
1004 : method, unit,
1005 : ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1006 :
1007 0 : msg = systemd_new_method(method);
1008 0 : CRM_ASSERT(msg != NULL);
1009 :
1010 : /* (ss) */
1011 : {
1012 0 : const char *replace_s = "replace";
1013 0 : char *name = systemd_service_name(op->agent,
1014 0 : pcmk__str_eq(op->action,
1015 : PCMK_ACTION_META_DATA,
1016 : pcmk__str_none));
1017 :
1018 0 : CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
1019 0 : CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
1020 :
1021 0 : free(name);
1022 : }
1023 :
1024 0 : if (op->synchronous) {
1025 0 : reply = systemd_send_recv(msg, NULL, op->timeout);
1026 0 : dbus_message_unref(msg);
1027 0 : process_unit_method_reply(reply, op);
1028 0 : if (reply != NULL) {
1029 0 : dbus_message_unref(reply);
1030 : }
1031 :
1032 : } else {
1033 0 : DBusPendingCall *pending = systemd_send(msg, unit_method_complete, op,
1034 : op->timeout);
1035 :
1036 0 : dbus_message_unref(msg);
1037 0 : if (pending == NULL) {
1038 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1039 : "Unable to send DBus message");
1040 0 : services__finalize_async_op(op);
1041 :
1042 : } else {
1043 0 : services_set_op_pending(op, pending);
1044 : }
1045 : }
1046 : }
1047 :
1048 : static gboolean
1049 0 : systemd_timeout_callback(gpointer p)
1050 : {
1051 0 : svc_action_t * op = p;
1052 :
1053 0 : op->opaque->timerid = 0;
1054 0 : crm_info("%s action for systemd unit %s named '%s' timed out",
1055 : op->action, op->agent, op->rsc);
1056 0 : services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
1057 : "%s action for systemd unit %s "
1058 : "did not complete in time", op->action, op->agent);
1059 0 : services__finalize_async_op(op);
1060 0 : return FALSE;
1061 : }
1062 :
1063 : /*!
1064 : * \internal
1065 : * \brief Execute a systemd action
1066 : *
1067 : * \param[in,out] op Action to execute
1068 : *
1069 : * \return Standard Pacemaker return code
1070 : * \retval EBUSY Recurring operation could not be initiated
1071 : * \retval pcmk_rc_error Synchronous action failed
1072 : * \retval pcmk_rc_ok Synchronous action succeeded, or asynchronous action
1073 : * should not be freed (because it's pending or because
1074 : * it failed to execute and was already freed)
1075 : *
1076 : * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
1077 : * caller is responsible for freeing the action.
1078 : */
1079 : int
1080 0 : services__execute_systemd(svc_action_t *op)
1081 : {
1082 0 : CRM_ASSERT(op != NULL);
1083 :
1084 0 : if ((op->action == NULL) || (op->agent == NULL)) {
1085 0 : services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
1086 : "Bug in action caller");
1087 0 : goto done;
1088 : }
1089 :
1090 0 : if (!systemd_init()) {
1091 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1092 : "No DBus connection");
1093 0 : goto done;
1094 : }
1095 :
1096 0 : crm_debug("Performing %ssynchronous %s op on systemd unit %s%s%s",
1097 : (op->synchronous? "" : "a"), op->action, op->agent,
1098 : ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1099 :
1100 0 : if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1101 0 : op->stdout_data = systemd_unit_metadata(op->agent, op->timeout);
1102 0 : services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1103 0 : goto done;
1104 : }
1105 :
1106 : /* invoke_unit_by_name() should always override these values, which are here
1107 : * just as a fail-safe in case there are any code paths that neglect to
1108 : */
1109 0 : services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1110 : "Bug in service library");
1111 :
1112 0 : if (invoke_unit_by_name(op->agent, op, NULL) == pcmk_rc_ok) {
1113 0 : op->opaque->timerid = g_timeout_add(op->timeout + 5000,
1114 : systemd_timeout_callback, op);
1115 0 : services_add_inflight_op(op);
1116 0 : return pcmk_rc_ok;
1117 : }
1118 :
1119 0 : done:
1120 0 : if (op->synchronous) {
1121 0 : return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
1122 : } else {
1123 0 : return services__finalize_async_op(op);
1124 : }
1125 : }
|