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 	{
(1) Event dereference_in_call: Function "g_string_append" dereferences "buf".
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);
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 	
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);
(1) Event deref_ptr_in_call: Dereferencing pointer "filename". [details]
Also see events: [check_after_deref]
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 	
CID (unavailable; MK=2b0c51563fd8a3374fcc1d9908a2d62a) (#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_in_call]
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