1    	/*
2    	 * Copyright 2014-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	
14   	#include <crm/crm.h>
15   	#include <dbus/dbus.h>
16   	#include <pcmk-dbus.h>
17   	
18   	/*
19   	 * DBus message dispatch
20   	 */
21   	
22   	// List of DBus connections (DBusConnection*) with messages available
23   	static GList *conn_dispatches = NULL;
24   	
25   	/*!
26   	 * \internal
27   	 * \brief Save an indication that DBus messages need dispatching
28   	 *
29   	 * \param[in] connection  DBus connection with messages to dispatch
30   	 * \param[in] new_status  Dispatch status as reported by DBus library
31   	 * \param[in] data        Ignored
32   	 *
33   	 * \note This is suitable to be used as a DBus status dispatch function.
34   	 *       As mentioned in the DBus documentation, dbus_connection_dispatch() must
35   	 *       not be called from within this function, and any re-entrancy is a bad
36   	 *       idea. Instead, this should just flag the main loop that messages need
37   	 *       to be dispatched.
38   	 */
39   	static void
40   	update_dispatch_status(DBusConnection *connection,
41   	                       DBusDispatchStatus new_status, void *data)
42   	{
43   	    if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
44   	        pcmk__trace("DBus connection has messages available for dispatch");
45   	        conn_dispatches = g_list_prepend(conn_dispatches, connection);
46   	    } else {
47   	        pcmk__trace("DBus connection has no messages available for dispatch "
48   	                    "(status %d)",
49   	                    new_status);
50   	    }
51   	}
52   	
53   	/*!
54   	 * \internal
55   	 * \brief Dispatch available messages on all DBus connections
56   	 */
57   	static void
58   	dispatch_messages(void)
59   	{
(1) Event path: Condition "gIter != NULL", taking true branch.
(4) Event path: Condition "gIter != NULL", taking false branch.
60   	    for (GList *gIter = conn_dispatches; gIter != NULL; gIter = gIter->next) {
61   	        DBusConnection *connection = gIter->data;
62   	
(2) Event path: Condition "dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS", taking false branch.
63   	        while (dbus_connection_get_dispatch_status(connection)
64   	               == DBUS_DISPATCH_DATA_REMAINS) {
65   	            pcmk__trace("Dispatching available messages on DBus connection");
66   	            dbus_connection_dispatch(connection);
67   	        }
(3) Event path: Jumping back to the beginning of the loop.
68   	    }
69   	
CID (unavailable; MK=da2441d9d355f93a1d432a23dfd2d894) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(5) Event assign_union_field: The union field "in" of "_pp" is written.
(6) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
70   	    g_clear_pointer(&conn_dispatches, g_list_free);
71   	}
72   	
73   	
74   	/*
75   	 * DBus file descriptor watches
76   	 *
77   	 * The DBus library allows the caller to register functions for the library to
78   	 * use for file descriptor notifications via a main loop.
79   	 */
80   	
81   	/* Copied from dbus-watch.c */
82   	static const char*
83   	dbus_watch_flags_to_string(int flags)
84   	{
85   	    const char *watch_type;
86   	
87   	    if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) {
88   	        watch_type = "read/write";
89   	    } else if (flags & DBUS_WATCH_READABLE) {
90   	        watch_type = "read";
91   	    } else if (flags & DBUS_WATCH_WRITABLE) {
92   	        watch_type = "write";
93   	    } else {
94   	        watch_type = "neither read nor write";
95   	    }
96   	    return watch_type;
97   	}
98   	
99   	/*!
100  	 * \internal
101  	 * \brief Dispatch data available on a DBus file descriptor watch
102  	 *
103  	 * \param[in,out] userdata  Pointer to the DBus watch
104  	 *
105  	 * \return Always 0
106  	 * \note This is suitable for use as a dispatch function in
107  	 *       struct mainloop_fd_callbacks (which means that a negative return value
108  	 *       would indicate the file descriptor is no longer required).
109  	 */
110  	static int
111  	dispatch_fd_data(gpointer userdata)
112  	{
113  	    bool oom = FALSE;
114  	    DBusWatch *watch = userdata;
115  	    int flags = dbus_watch_get_flags(watch);
116  	    bool enabled = dbus_watch_get_enabled (watch);
117  	
118  	    pcmk__trace("Dispatching DBus watch for file descriptor %d "
119  	                "with flags %#x (%s)",
120  	                dbus_watch_get_unix_fd(watch), flags,
121  	                dbus_watch_flags_to_string(flags));
122  	
123  	    if (enabled && (flags & (DBUS_WATCH_READABLE|DBUS_WATCH_WRITABLE))) {
124  	        oom = !dbus_watch_handle(watch, flags);
125  	
126  	    } else if (enabled) {
127  	        oom = !dbus_watch_handle(watch, DBUS_WATCH_ERROR);
128  	    }
129  	
130  	    if (flags != dbus_watch_get_flags(watch)) {
131  	        flags = dbus_watch_get_flags(watch);
132  	        pcmk__trace("Dispatched DBus file descriptor watch: now %#x (%s)",
133  	                    flags, dbus_watch_flags_to_string(flags));
134  	    }
135  	
136  	    if (oom) {
137  	        pcmk__crit("Could not dispatch DBus file descriptor data: Out of "
138  	                   "memory");
139  	    } else {
140  	        dispatch_messages();
141  	    }
142  	    return 0;
143  	}
144  	
145  	static void
146  	watch_fd_closed(gpointer userdata)
147  	{
148  	    pcmk__trace("DBus watch for file descriptor %d is now closed",
149  	                dbus_watch_get_unix_fd((DBusWatch *) userdata));
150  	}
151  	
152  	static struct mainloop_fd_callbacks pcmk_dbus_cb = {
153  	    .dispatch = dispatch_fd_data,
154  	    .destroy = watch_fd_closed,
155  	};
156  	
157  	static dbus_bool_t
158  	add_dbus_watch(DBusWatch *watch, void *data)
159  	{
160  	    int fd = dbus_watch_get_unix_fd(watch);
161  	
162  	    mainloop_io_t *client = mainloop_add_fd("dbus", G_PRIORITY_DEFAULT, fd,
163  	                                            watch, &pcmk_dbus_cb);
164  	
165  	    pcmk__trace("Added DBus watch for file descriptor %d", fd);
166  	    dbus_watch_set_data(watch, client, NULL);
167  	    return TRUE;
168  	}
169  	
170  	static void
171  	toggle_dbus_watch(DBusWatch *watch, void *data)
172  	{
173  	    // @TODO Should this do something more?
174  	    pcmk__debug("DBus watch for file descriptor %d is now %s",
175  	                dbus_watch_get_unix_fd(watch),
176  	                (dbus_watch_get_enabled(watch)? "enabled" : "disabled"));
177  	}
178  	
179  	static void
180  	remove_dbus_watch(DBusWatch *watch, void *data)
181  	{
182  	    pcmk__trace("Removed DBus watch for file descriptor %d",
183  	                dbus_watch_get_unix_fd(watch));
184  	    mainloop_del_fd((mainloop_io_t *) dbus_watch_get_data(watch));
185  	}
186  	
187  	static void
188  	register_watch_functions(DBusConnection *connection)
189  	{
190  	    dbus_connection_set_watch_functions(connection, add_dbus_watch,
191  	                                        remove_dbus_watch,
192  	                                        toggle_dbus_watch, NULL, NULL);
193  	}
194  	
195  	/*
196  	 * DBus main loop timeouts
197  	 *
198  	 * The DBus library allows the caller to register functions for the library to
199  	 * use for managing timers via a main loop.
200  	 */
201  	
202  	static gboolean
203  	timer_popped(gpointer data)
204  	{
205  	    pcmk__debug("%dms DBus timer expired",
206  	                dbus_timeout_get_interval((DBusTimeout *) data));
207  	    dbus_timeout_handle(data);
208  	    return FALSE;
209  	}
210  	
211  	static dbus_bool_t
212  	add_dbus_timer(DBusTimeout *timeout, void *data)
213  	{
214  	    int interval_ms = dbus_timeout_get_interval(timeout);
215  	    guint id = pcmk__create_timer(interval_ms, timer_popped, timeout);
216  	
217  	    if (id) {
218  	        dbus_timeout_set_data(timeout, GUINT_TO_POINTER(id), NULL);
219  	    }
220  	    pcmk__trace("Added %dms DBus timer", interval_ms);
221  	    return TRUE;
222  	}
223  	
224  	static void
225  	remove_dbus_timer(DBusTimeout *timeout, void *data)
226  	{
227  	    void *vid = dbus_timeout_get_data(timeout);
228  	    guint id = GPOINTER_TO_UINT(vid);
229  	
230  	    pcmk__trace("Removing %dms DBus timer", dbus_timeout_get_interval(timeout));
231  	    if (id) {
232  	        g_source_remove(id);
233  	        dbus_timeout_set_data(timeout, 0, NULL);
234  	    }
235  	}
236  	
237  	static void
238  	toggle_dbus_timer(DBusTimeout *timeout, void *data)
239  	{
240  	    bool enabled = dbus_timeout_get_enabled(timeout);
241  	
242  	    pcmk__trace("Toggling %dms DBus timer %s",
243  	                dbus_timeout_get_interval(timeout), (enabled? "off": "on"));
244  	    if (enabled) {
245  	        add_dbus_timer(timeout, data);
246  	    } else {
247  	        remove_dbus_timer(timeout, data);
248  	    }
249  	}
250  	
251  	static void
252  	register_timer_functions(DBusConnection *connection)
253  	{
254  	    dbus_connection_set_timeout_functions(connection, add_dbus_timer,
255  	                                          remove_dbus_timer,
256  	                                          toggle_dbus_timer, NULL, NULL);
257  	}
258  	
259  	/*
260  	 * General DBus utilities
261  	 */
262  	
263  	DBusConnection *
264  	pcmk_dbus_connect(void)
265  	{
266  	    DBusError err;
267  	    DBusConnection *connection;
268  	
269  	    dbus_error_init(&err);
270  	    connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
271  	    if (dbus_error_is_set(&err)) {
272  	        pcmk__err("Could not connect to DBus: %s", err.message);
273  	        dbus_error_free(&err);
274  	        return NULL;
275  	    }
276  	    if (connection == NULL) {
277  	        return NULL;
278  	    }
279  	
280  	    /* Tell libdbus not to exit the process when a disconnect happens. This
281  	     * defaults to FALSE but is toggled on by the dbus_bus_get() call above.
282  	     */
283  	    dbus_connection_set_exit_on_disconnect(connection, FALSE);
284  	
285  	    // Set custom handlers for various situations
286  	    register_timer_functions(connection);
287  	    register_watch_functions(connection);
288  	    dbus_connection_set_dispatch_status_function(connection,
289  	                                                 update_dispatch_status,
290  	                                                 NULL, NULL);
291  	
292  	    // Call the dispatch function to check for any messages waiting already
293  	    update_dispatch_status(connection,
294  	                           dbus_connection_get_dispatch_status(connection),
295  	                           NULL);
296  	    return connection;
297  	}
298  	
299  	void
300  	pcmk_dbus_disconnect(DBusConnection *connection)
301  	{
302  	    /* We acquire our dbus connection with dbus_bus_get(), which makes it a
303  	     * shared connection.  Therefore, we can't close or free it here.  The
304  	     * best we can do is decrement the reference count so dbus knows when
305  	     * there are no more clients connected to it.
306  	     */
307  	    dbus_connection_unref(connection);
308  	}
309  	
310  	// Custom DBus error names to use
311  	#define ERR_NO_REQUEST           "org.clusterlabs.pacemaker.NoRequest"
312  	#define ERR_NO_REPLY             "org.clusterlabs.pacemaker.NoReply"
313  	#define ERR_INVALID_REPLY        "org.clusterlabs.pacemaker.InvalidReply"
314  	#define ERR_INVALID_REPLY_METHOD "org.clusterlabs.pacemaker.InvalidReply.Method"
315  	#define ERR_INVALID_REPLY_SIGNAL "org.clusterlabs.pacemaker.InvalidReply.Signal"
316  	#define ERR_INVALID_REPLY_TYPE   "org.clusterlabs.pacemaker.InvalidReply.Type"
317  	#define ERR_SEND_FAILED          "org.clusterlabs.pacemaker.SendFailed"
318  	
319  	/*!
320  	 * \internal
321  	 * \brief Check whether a DBus reply indicates an error occurred
322  	 *
323  	 * \param[in]  pending If non-NULL, indicates that a DBus request was sent
324  	 * \param[in]  reply   Reply received from DBus
325  	 * \param[out] ret     If non-NULL, will be set to DBus error, if any
326  	 *
327  	 * \return TRUE if an error was found, FALSE otherwise
328  	 *
329  	 * \note Following the DBus API convention, a TRUE return is exactly equivalent
330  	 *       to ret being set. If ret is provided and this function returns TRUE,
331  	 *       the caller is responsible for calling dbus_error_free() on ret when
332  	 *       done using it.
333  	 */
334  	bool
335  	pcmk_dbus_find_error(const DBusPendingCall *pending, DBusMessage *reply,
336  	                     DBusError *ret)
337  	{
338  	    DBusError error;
339  	
340  	    dbus_error_init(&error);
341  	
342  	    if (pending == NULL) {
343  	        dbus_set_error_const(&error, ERR_NO_REQUEST, "No request sent");
344  	
345  	    } else if (reply == NULL) {
346  	        dbus_set_error_const(&error, ERR_NO_REPLY, "No reply");
347  	
348  	    } else {
349  	        DBusMessageIter args;
350  	        int dtype = dbus_message_get_type(reply);
351  	
352  	        switch (dtype) {
353  	            case DBUS_MESSAGE_TYPE_METHOD_RETURN:
354  	                dbus_message_iter_init(reply, &args);
355  	                pcmk__if_tracing(
356  	                    {
357  	                        char *sig = dbus_message_iter_get_signature(&args);
358  	
359  	                        pcmk__trace("Received DBus reply with argument type "
360  	                                    "'%s'",
361  	                                    pcmk__s(sig, "(bad signature)"));
362  	                        dbus_free(sig);
363  	                    },
364  	                    {}
365  	                );
366  	                break;
367  	            case DBUS_MESSAGE_TYPE_INVALID:
368  	                dbus_set_error_const(&error, ERR_INVALID_REPLY,
369  	                                     "Invalid reply");
370  	                break;
371  	            case DBUS_MESSAGE_TYPE_METHOD_CALL:
372  	                dbus_set_error_const(&error, ERR_INVALID_REPLY_METHOD,
373  	                                     "Invalid reply (method call)");
374  	                break;
375  	            case DBUS_MESSAGE_TYPE_SIGNAL:
376  	                dbus_set_error_const(&error, ERR_INVALID_REPLY_SIGNAL,
377  	                                     "Invalid reply (signal)");
378  	                break;
379  	            case DBUS_MESSAGE_TYPE_ERROR:
380  	                dbus_set_error_from_message(&error, reply);
381  	                break;
382  	            default:
383  	                dbus_set_error(&error, ERR_INVALID_REPLY_TYPE,
384  	                               "Unknown reply type %d", dtype);
385  	        }
386  	    }
387  	
388  	    if (dbus_error_is_set(&error)) {
389  	        pcmk__trace("DBus reply indicated error '%s' (%s)",
390  	                    error.name, error.message);
391  	        if (ret) {
392  	            dbus_error_init(ret);
393  	            dbus_move_error(&error, ret);
394  	        } else {
395  	            dbus_error_free(&error);
396  	        }
397  	        return TRUE;
398  	    }
399  	
400  	    return FALSE;
401  	}
402  	
403  	/*!
404  	 * \internal
405  	 * \brief Send a DBus request and wait for the reply
406  	 *
407  	 * \param[in,out] msg         DBus request to send
408  	 * \param[in,out] connection  DBus connection to use
409  	 * \param[out]    error       If non-NULL, will be set to error, if any
410  	 * \param[in]     timeout     Timeout to use for request
411  	 *
412  	 * \return DBus reply
413  	 *
414  	 * \note If error is non-NULL, it is initialized, so the caller may always use
415  	 *       dbus_error_is_set() to determine whether an error occurred; the caller
416  	 *       is responsible for calling dbus_error_free() in this case.
417  	 */
418  	DBusMessage *
419  	pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection,
420  	                    DBusError *error, int timeout)
421  	{
422  	    const char *method = NULL;
423  	    DBusMessage *reply = NULL;
424  	    DBusPendingCall* pending = NULL;
425  	
426  	    pcmk__assert(dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
427  	    method = dbus_message_get_member (msg);
428  	
429  	    /* Ensure caller can reliably check whether error is set */
430  	    if (error) {
431  	        dbus_error_init(error);
432  	    }
433  	
434  	    if (timeout <= 0) {
435  	        /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
436  	        timeout = DBUS_TIMEOUT_USE_DEFAULT;
437  	    }
438  	
439  	    // send message and get a handle for a reply
440  	    if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
441  	        if (error) {
442  	            dbus_set_error(error, ERR_SEND_FAILED,
443  	                           "Could not queue DBus '%s' request", method);
444  	        }
445  	        return NULL;
446  	    }
447  	
448  	    dbus_connection_flush(connection);
449  	
450  	    if (pending) {
451  	        /* block until we receive a reply */
452  	        dbus_pending_call_block(pending);
453  	
454  	        /* get the reply message */
455  	        reply = dbus_pending_call_steal_reply(pending);
456  	    }
457  	
458  	    (void) pcmk_dbus_find_error(pending, reply, error);
459  	
460  	    if (pending) {
461  	        /* free the pending message handle */
462  	        dbus_pending_call_unref(pending);
463  	    }
464  	
465  	    return reply;
466  	}
467  	
468  	/*!
469  	 * \internal
470  	 * \brief Send a DBus message with a callback for the reply
471  	 *
472  	 * \param[in,out] msg         DBus message to send
473  	 * \param[in,out] connection  DBus connection to send on
474  	 * \param[in]     done        Function to call when pending call completes
475  	 * \param[in]     user_data   Data to pass to done callback
476  	 *
477  	 * \return Handle for reply on success, NULL on error
478  	 * \note The caller can assume that the done callback is called always and
479  	 *       only when the return value is non-NULL. (This allows the caller to
480  	 *       know where it should free dynamically allocated user_data.)
481  	 */
482  	DBusPendingCall *
483  	pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection,
484  	               void (*done)(DBusPendingCall *pending, void *user_data),
485  	               void *user_data, int timeout)
486  	{
487  	    const char *method = NULL;
488  	    DBusPendingCall* pending = NULL;
489  	
490  	    pcmk__assert(done != NULL);
491  	    pcmk__assert(dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
492  	    method = dbus_message_get_member(msg);
493  	
494  	    if (timeout <= 0) {
495  	        /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
496  	        timeout = DBUS_TIMEOUT_USE_DEFAULT;
497  	    }
498  	
499  	    // send message and get a handle for a reply
500  	    if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
501  	        pcmk__err("Could not send DBus %s message: failed", method);
502  	        return NULL;
503  	
504  	    } else if (pending == NULL) {
505  	        pcmk__err("Could not send DBus %s message: connection may be closed",
506  	                  method);
507  	        return NULL;
508  	    }
509  	
510  	    if (dbus_pending_call_get_completed(pending)) {
511  	        pcmk__info("DBus %s message completed too soon", method);
512  	        /* Calling done() directly in this case instead of setting notify below
513  	         * breaks things
514  	         */
515  	    }
516  	    if (!dbus_pending_call_set_notify(pending, done, user_data, NULL)) {
517  	        return NULL;
518  	    }
519  	    return pending;
520  	}
521  	
522  	bool
523  	pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected,
524  	                     const char *function, int line)
525  	{
526  	    int dtype = 0;
527  	    DBusMessageIter lfield;
528  	
529  	    if (field == NULL) {
530  	        if (dbus_message_iter_init(msg, &lfield)) {
531  	            field = &lfield;
532  	        }
533  	    }
534  	
535  	    if (field == NULL) {
536  	        do_crm_log_alias(LOG_INFO, __FILE__, function, line,
537  	                         "DBus reply has empty parameter list (expected '%c')",
538  	                         expected);
539  	        return FALSE;
540  	    }
541  	
542  	    dtype = dbus_message_iter_get_arg_type(field);
543  	
544  	    if (dtype != expected) {
545  	        DBusMessageIter args;
546  	        char *sig;
547  	
548  	        dbus_message_iter_init(msg, &args);
549  	        sig = dbus_message_iter_get_signature(&args);
550  	        do_crm_log_alias(LOG_INFO, __FILE__, function, line,
551  	                         "DBus reply has unexpected type "
552  	                         "(expected '%c' not '%c' in '%s')",
553  	                         expected, dtype, sig);
554  	        dbus_free(sig);
555  	        return FALSE;
556  	    }
557  	
558  	    return TRUE;
559  	}
560  	
561  	
562  	/*
563  	 * Property queries
564  	 */
565  	
566  	/* DBus APIs often provide queryable properties that use this standard
567  	 * interface. See:
568  	 * https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
569  	 */
570  	#define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties"
571  	
572  	// Callback prototype for when a DBus property query result is received
573  	typedef void (*property_callback_func)(const char *name,  // Property name
574  	                                       const char *value, // Property value
575  	                                       void *userdata);   // Caller-provided data
576  	
577  	// Data needed by DBus property queries
578  	struct property_query {
579  	    char *name;         // Property name being queried
580  	    char *target;       // Name of DBus bus that query should be sent to
581  	    char *object;       // DBus object path for object with the property
582  	    void *userdata;     // Caller-provided data to supply to callback
583  	    property_callback_func callback; // Function to call when result is received
584  	};
585  	
586  	static void
587  	free_property_query(struct property_query *data)
588  	{
589  	    free(data->target);
590  	    free(data->object);
591  	    free(data->name);
592  	    free(data);
593  	}
594  	
595  	static char *
596  	handle_query_result(DBusMessage *reply, struct property_query *data)
597  	{
598  	    DBusError error;
599  	    char *output = NULL;
600  	    DBusMessageIter args;
601  	    DBusMessageIter variant_iter;
602  	    DBusBasicValue value;
603  	
604  	    dbus_error_init(&error);
605  	
606  	    // First, check if the reply contains an error
607  	    if (pcmk_dbus_find_error((void*)&error, reply, &error)) {
608  	        pcmk__err("DBus query for %s property '%s' failed: %s", data->object,
609  	                  data->name, error.message);
610  	        dbus_error_free(&error);
611  	        goto cleanup;
612  	    }
613  	
614  	    // The lone output argument should be a DBus variant type
615  	    dbus_message_iter_init(reply, &args);
616  	    if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_VARIANT,
617  	                              __func__, __LINE__)) {
618  	        pcmk__err("DBus query for %s property '%s' failed: Unexpected reply "
619  	                  "type",
620  	                  data->object, data->name);
621  	        goto cleanup;
622  	    }
623  	
624  	    // The variant should be a string
625  	    dbus_message_iter_recurse(&args, &variant_iter);
626  	    if (!pcmk_dbus_type_check(reply, &variant_iter, DBUS_TYPE_STRING,
627  	                              __func__, __LINE__)) {
628  	        pcmk__err("DBus query for %s property '%s' failed: Unexpected variant "
629  	                  "type",
630  	                  data->object, data->name);
631  	        goto cleanup;
632  	    }
633  	    dbus_message_iter_get_basic(&variant_iter, &value);
634  	
635  	    // There should be no more arguments (in variant or reply)
636  	    dbus_message_iter_next(&variant_iter);
637  	    if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_INVALID) {
638  	        pcmk__err("DBus query for %s property '%s' failed: Too many arguments "
639  	                  "in reply",
640  	                  data->object, data->name);
641  	        goto cleanup;
642  	    }
643  	    dbus_message_iter_next(&args);
644  	    if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_INVALID) {
645  	        pcmk__err("DBus query for %s property '%s' failed: Too many arguments "
646  	                  "in reply",
647  	                  data->object, data->name);
648  	        goto cleanup;
649  	    }
650  	
651  	    pcmk__trace("DBus query result for %s: %s='%s'",
652  	                data->object, data->name, pcmk__s(value.str, ""));
653  	
654  	    if (data->callback) {   // Query was asynchronous
655  	        data->callback(data->name, (value.str? value.str : ""), data->userdata);
656  	
657  	    } else {                // Query was synchronous
658  	        output = strdup(value.str? value.str : "");
659  	    }
660  	
661  	  cleanup:
662  	    free_property_query(data);
663  	    return output;
664  	}
665  	
666  	static void
667  	async_query_result_cb(DBusPendingCall *pending, void *user_data)
668  	{
669  	    DBusMessage *reply = NULL;
670  	    char *value = NULL;
671  	
672  	    if (pending) {
673  	        reply = dbus_pending_call_steal_reply(pending);
674  	    }
675  	
676  	    value = handle_query_result(reply, user_data);
677  	    free(value);
678  	
679  	    if (reply) {
680  	        dbus_message_unref(reply);
681  	    }
682  	}
683  	
684  	/*!
685  	 * \internal
686  	 * \brief Query a property on a DBus object
687  	 *
688  	 * \param[in,out] connection  An active connection to DBus
689  	 * \param[in]     target      DBus name that the query should be sent to
690  	 * \param[in]     obj         DBus object path for object with the property
691  	 * \param[in]     iface       DBus interface for property to query
692  	 * \param[in]     name        Name of property to query
693  	 * \param[in]     callback    If not NULL, perform query asynchronously and call
694  	 *                            this function when query completes
695  	 * \param[in,out] userdata    Caller-provided data to provide to \p callback
696  	 * \param[out]    pending     If \p callback is not NULL, this will be set to
697  	 *                            handle for the reply (or NULL on error)
698  	 * \param[in]     timeout     Abort query if it takes longer than this (ms)
699  	 *
700  	 * \return NULL if \p callback is non-NULL (i.e. asynchronous), otherwise a
701  	 *         newly allocated string with property value
702  	 * \note It is the caller's responsibility to free the result with free().
703  	 */
704  	char *
705  	pcmk_dbus_get_property(DBusConnection *connection, const char *target,
706  	                       const char *obj, const gchar * iface, const char *name,
707  	                       property_callback_func callback, void *userdata,
708  	                       DBusPendingCall **pending, int timeout)
709  	{
710  	    DBusMessage *msg;
711  	    char *output = NULL;
712  	    struct property_query *query_data = NULL;
713  	
714  	    CRM_CHECK((connection != NULL) && (target != NULL) && (obj != NULL)
715  	              && (iface != NULL) && (name != NULL), return NULL);
716  	
717  	    pcmk__trace("Querying DBus %s for %s property '%s'", target, obj, name);
718  	
719  	    // Create a new message to use to invoke method
720  	    msg = dbus_message_new_method_call(target, obj, BUS_PROPERTY_IFACE, "Get");
721  	    if (msg == NULL) {
722  	        pcmk__err("DBus query for %s property '%s' failed: Unable to create "
723  	                  "message",
724  	                  obj, name);
725  	        return NULL;
726  	    }
727  	
728  	    // Add the interface name and property name as message arguments
729  	    if (!dbus_message_append_args(msg,
730  	                                  DBUS_TYPE_STRING, &iface,
731  	                                  DBUS_TYPE_STRING, &name,
732  	                                  DBUS_TYPE_INVALID)) {
733  	        pcmk__err("DBus query for %s property '%s' failed: Could not append "
734  	                  "arguments",
735  	                  obj, name);
736  	        dbus_message_unref(msg);
737  	        return NULL;
738  	    }
739  	
740  	    query_data = malloc(sizeof(struct property_query));
741  	    if (query_data == NULL) {
742  	        pcmk__crit("DBus query for %s property '%s' failed: Out of memory", obj,
743  	                   name);
744  	        dbus_message_unref(msg);
745  	        return NULL;
746  	    }
747  	
748  	    query_data->target = strdup(target);
749  	    query_data->object = strdup(obj);
750  	    query_data->callback = callback;
751  	    query_data->userdata = userdata;
752  	    query_data->name = strdup(name);
753  	    CRM_CHECK((query_data->target != NULL)
754  	                  && (query_data->object != NULL)
755  	                  && (query_data->name != NULL),
756  	              free_property_query(query_data);
757  	              dbus_message_unref(msg);
758  	              return NULL);
759  	
760  	    if (query_data->callback) { // Asynchronous
761  	        DBusPendingCall *local_pending;
762  	
763  	        local_pending = pcmk_dbus_send(msg, connection, async_query_result_cb,
764  	                                       query_data, timeout);
765  	        if (local_pending == NULL) {
766  	            // async_query_result_cb() was not called in this case
767  	            g_clear_pointer(&query_data, free_property_query);
768  	        }
769  	
770  	        if (pending) {
771  	            *pending = local_pending;
772  	        }
773  	
774  	    } else { // Synchronous
775  	        DBusMessage *reply = pcmk_dbus_send_recv(msg, connection, NULL,
776  	                                                 timeout);
777  	
778  	        output = handle_query_result(reply, query_data);
779  	
780  	        if (reply) {
781  	            dbus_message_unref(reply);
782  	        }
783  	    }
784  	
785  	    dbus_message_unref(msg);
786  	
787  	    return output;
788  	}
789