summaryrefslogtreecommitdiffstats
path: root/lib/services/dbus.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/services/dbus.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/services/dbus.c')
-rw-r--r--lib/services/dbus.c776
1 files changed, 776 insertions, 0 deletions
diff --git a/lib/services/dbus.c b/lib/services/dbus.c
new file mode 100644
index 0000000..f052c0a
--- /dev/null
+++ b/lib/services/dbus.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright 2014-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <crm/crm.h>
+#include <dbus/dbus.h>
+#include <pcmk-dbus.h>
+
+/*
+ * DBus message dispatch
+ */
+
+// List of DBus connections (DBusConnection*) with messages available
+static GList *conn_dispatches = NULL;
+
+/*!
+ * \internal
+ * \brief Save an indication that DBus messages need dispatching
+ *
+ * \param[in] connection DBus connection with messages to dispatch
+ * \param[in] new_status Dispatch status as reported by DBus library
+ * \param[in] data Ignored
+ *
+ * \note This is suitable to be used as a DBus status dispatch function.
+ * As mentioned in the DBus documentation, dbus_connection_dispatch() must
+ * not be called from within this function, and any re-entrancy is a bad
+ * idea. Instead, this should just flag the main loop that messages need
+ * to be dispatched.
+ */
+static void
+update_dispatch_status(DBusConnection *connection,
+ DBusDispatchStatus new_status, void *data)
+{
+ if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
+ crm_trace("DBus connection has messages available for dispatch");
+ conn_dispatches = g_list_prepend(conn_dispatches, connection);
+ } else {
+ crm_trace("DBus connection has no messages available for dispatch "
+ "(status %d)", new_status);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Dispatch available messages on all DBus connections
+ */
+static void
+dispatch_messages(void)
+{
+ for (GList *gIter = conn_dispatches; gIter != NULL; gIter = gIter->next) {
+ DBusConnection *connection = gIter->data;
+
+ while (dbus_connection_get_dispatch_status(connection)
+ == DBUS_DISPATCH_DATA_REMAINS) {
+ crm_trace("Dispatching available messages on DBus connection");
+ dbus_connection_dispatch(connection);
+ }
+ }
+ g_list_free(conn_dispatches);
+ conn_dispatches = NULL;
+}
+
+
+/*
+ * DBus file descriptor watches
+ *
+ * The DBus library allows the caller to register functions for the library to
+ * use for file descriptor notifications via a main loop.
+ */
+
+/* Copied from dbus-watch.c */
+static const char*
+dbus_watch_flags_to_string(int flags)
+{
+ const char *watch_type;
+
+ if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) {
+ watch_type = "read/write";
+ } else if (flags & DBUS_WATCH_READABLE) {
+ watch_type = "read";
+ } else if (flags & DBUS_WATCH_WRITABLE) {
+ watch_type = "write";
+ } else {
+ watch_type = "neither read nor write";
+ }
+ return watch_type;
+}
+
+/*!
+ * \internal
+ * \brief Dispatch data available on a DBus file descriptor watch
+ *
+ * \param[in,out] userdata Pointer to the DBus watch
+ *
+ * \return Always 0
+ * \note This is suitable for use as a dispatch function in
+ * struct mainloop_fd_callbacks (which means that a negative return value
+ * would indicate the file descriptor is no longer required).
+ */
+static int
+dispatch_fd_data(gpointer userdata)
+{
+ bool oom = FALSE;
+ DBusWatch *watch = userdata;
+ int flags = dbus_watch_get_flags(watch);
+ bool enabled = dbus_watch_get_enabled (watch);
+
+ crm_trace("Dispatching DBus watch for file descriptor %d "
+ "with flags %#x (%s)",
+ dbus_watch_get_unix_fd(watch), flags,
+ dbus_watch_flags_to_string(flags));
+
+ if (enabled && (flags & (DBUS_WATCH_READABLE|DBUS_WATCH_WRITABLE))) {
+ oom = !dbus_watch_handle(watch, flags);
+
+ } else if (enabled) {
+ oom = !dbus_watch_handle(watch, DBUS_WATCH_ERROR);
+ }
+
+ if (flags != dbus_watch_get_flags(watch)) {
+ flags = dbus_watch_get_flags(watch);
+ crm_trace("Dispatched DBus file descriptor watch: now %#x (%s)",
+ flags, dbus_watch_flags_to_string(flags));
+ }
+
+ if (oom) {
+ crm_crit("Could not dispatch DBus file descriptor data: Out of memory");
+ } else {
+ dispatch_messages();
+ }
+ return 0;
+}
+
+static void
+watch_fd_closed(gpointer userdata)
+{
+ crm_trace("DBus watch for file descriptor %d is now closed",
+ dbus_watch_get_unix_fd((DBusWatch *) userdata));
+}
+
+static struct mainloop_fd_callbacks pcmk_dbus_cb = {
+ .dispatch = dispatch_fd_data,
+ .destroy = watch_fd_closed,
+};
+
+static dbus_bool_t
+add_dbus_watch(DBusWatch *watch, void *data)
+{
+ int fd = dbus_watch_get_unix_fd(watch);
+
+ mainloop_io_t *client = mainloop_add_fd("dbus", G_PRIORITY_DEFAULT, fd,
+ watch, &pcmk_dbus_cb);
+
+ crm_trace("Added DBus watch for file descriptor %d", fd);
+ dbus_watch_set_data(watch, client, NULL);
+ return TRUE;
+}
+
+static void
+toggle_dbus_watch(DBusWatch *watch, void *data)
+{
+ // @TODO Should this do something more?
+ crm_debug("DBus watch for file descriptor %d is now %s",
+ dbus_watch_get_unix_fd(watch),
+ (dbus_watch_get_enabled(watch)? "enabled" : "disabled"));
+}
+
+static void
+remove_dbus_watch(DBusWatch *watch, void *data)
+{
+ crm_trace("Removed DBus watch for file descriptor %d",
+ dbus_watch_get_unix_fd(watch));
+ mainloop_del_fd((mainloop_io_t *) dbus_watch_get_data(watch));
+}
+
+static void
+register_watch_functions(DBusConnection *connection)
+{
+ dbus_connection_set_watch_functions(connection, add_dbus_watch,
+ remove_dbus_watch,
+ toggle_dbus_watch, NULL, NULL);
+}
+
+/*
+ * DBus main loop timeouts
+ *
+ * The DBus library allows the caller to register functions for the library to
+ * use for managing timers via a main loop.
+ */
+
+static gboolean
+timer_popped(gpointer data)
+{
+ crm_debug("%dms DBus timer expired",
+ dbus_timeout_get_interval((DBusTimeout *) data));
+ dbus_timeout_handle(data);
+ return FALSE;
+}
+
+static dbus_bool_t
+add_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ int interval_ms = dbus_timeout_get_interval(timeout);
+ guint id = g_timeout_add(interval_ms, timer_popped, timeout);
+
+ if (id) {
+ dbus_timeout_set_data(timeout, GUINT_TO_POINTER(id), NULL);
+ }
+ crm_trace("Added %dms DBus timer", interval_ms);
+ return TRUE;
+}
+
+static void
+remove_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ void *vid = dbus_timeout_get_data(timeout);
+ guint id = GPOINTER_TO_UINT(vid);
+
+ crm_trace("Removing %dms DBus timer", dbus_timeout_get_interval(timeout));
+ if (id) {
+ g_source_remove(id);
+ dbus_timeout_set_data(timeout, 0, NULL);
+ }
+}
+
+static void
+toggle_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ bool enabled = dbus_timeout_get_enabled(timeout);
+
+ crm_trace("Toggling %dms DBus timer %s",
+ dbus_timeout_get_interval(timeout), (enabled? "off": "on"));
+ if (enabled) {
+ add_dbus_timer(timeout, data);
+ } else {
+ remove_dbus_timer(timeout, data);
+ }
+}
+
+static void
+register_timer_functions(DBusConnection *connection)
+{
+ dbus_connection_set_timeout_functions(connection, add_dbus_timer,
+ remove_dbus_timer,
+ toggle_dbus_timer, NULL, NULL);
+}
+
+/*
+ * General DBus utilities
+ */
+
+DBusConnection *
+pcmk_dbus_connect(void)
+{
+ DBusError err;
+ DBusConnection *connection;
+
+ dbus_error_init(&err);
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+ if (dbus_error_is_set(&err)) {
+ crm_err("Could not connect to DBus: %s", err.message);
+ dbus_error_free(&err);
+ return NULL;
+ }
+ if (connection == NULL) {
+ return NULL;
+ }
+
+ /* Tell libdbus not to exit the process when a disconnect happens. This
+ * defaults to FALSE but is toggled on by the dbus_bus_get() call above.
+ */
+ dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+ // Set custom handlers for various situations
+ register_timer_functions(connection);
+ register_watch_functions(connection);
+ dbus_connection_set_dispatch_status_function(connection,
+ update_dispatch_status,
+ NULL, NULL);
+
+ // Call the dispatch function to check for any messages waiting already
+ update_dispatch_status(connection,
+ dbus_connection_get_dispatch_status(connection),
+ NULL);
+ return connection;
+}
+
+void
+pcmk_dbus_disconnect(DBusConnection *connection)
+{
+ /* Per the DBus documentation, connections created with
+ * dbus_connection_open() are owned by libdbus and should never be closed.
+ *
+ * @TODO Should we call dbus_connection_unref() here?
+ */
+ return;
+}
+
+// Custom DBus error names to use
+#define ERR_NO_REQUEST "org.clusterlabs.pacemaker.NoRequest"
+#define ERR_NO_REPLY "org.clusterlabs.pacemaker.NoReply"
+#define ERR_INVALID_REPLY "org.clusterlabs.pacemaker.InvalidReply"
+#define ERR_INVALID_REPLY_METHOD "org.clusterlabs.pacemaker.InvalidReply.Method"
+#define ERR_INVALID_REPLY_SIGNAL "org.clusterlabs.pacemaker.InvalidReply.Signal"
+#define ERR_INVALID_REPLY_TYPE "org.clusterlabs.pacemaker.InvalidReply.Type"
+#define ERR_SEND_FAILED "org.clusterlabs.pacemaker.SendFailed"
+
+/*!
+ * \internal
+ * \brief Check whether a DBus reply indicates an error occurred
+ *
+ * \param[in] pending If non-NULL, indicates that a DBus request was sent
+ * \param[in] reply Reply received from DBus
+ * \param[out] ret If non-NULL, will be set to DBus error, if any
+ *
+ * \return TRUE if an error was found, FALSE otherwise
+ *
+ * \note Following the DBus API convention, a TRUE return is exactly equivalent
+ * to ret being set. If ret is provided and this function returns TRUE,
+ * the caller is responsible for calling dbus_error_free() on ret when
+ * done using it.
+ */
+bool
+pcmk_dbus_find_error(const DBusPendingCall *pending, DBusMessage *reply,
+ DBusError *ret)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (pending == NULL) {
+ dbus_set_error_const(&error, ERR_NO_REQUEST, "No request sent");
+
+ } else if (reply == NULL) {
+ dbus_set_error_const(&error, ERR_NO_REPLY, "No reply");
+
+ } else {
+ DBusMessageIter args;
+ int dtype = dbus_message_get_type(reply);
+
+ switch (dtype) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ {
+ char *sig = NULL;
+
+ dbus_message_iter_init(reply, &args);
+ crm_trace("Received DBus reply with argument type '%s'",
+ (sig = dbus_message_iter_get_signature(&args)));
+ if (sig != NULL) {
+ dbus_free(sig);
+ }
+ }
+ break;
+ case DBUS_MESSAGE_TYPE_INVALID:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY,
+ "Invalid reply");
+ break;
+ case DBUS_MESSAGE_TYPE_METHOD_CALL:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY_METHOD,
+ "Invalid reply (method call)");
+ break;
+ case DBUS_MESSAGE_TYPE_SIGNAL:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY_SIGNAL,
+ "Invalid reply (signal)");
+ break;
+ case DBUS_MESSAGE_TYPE_ERROR:
+ dbus_set_error_from_message(&error, reply);
+ break;
+ default:
+ dbus_set_error(&error, ERR_INVALID_REPLY_TYPE,
+ "Unknown reply type %d", dtype);
+ }
+ }
+
+ if (dbus_error_is_set(&error)) {
+ crm_trace("DBus reply indicated error '%s' (%s)",
+ error.name, error.message);
+ if (ret) {
+ dbus_error_init(ret);
+ dbus_move_error(&error, ret);
+ } else {
+ dbus_error_free(&error);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Send a DBus request and wait for the reply
+ *
+ * \param[in,out] msg DBus request to send
+ * \param[in,out] connection DBus connection to use
+ * \param[out] error If non-NULL, will be set to error, if any
+ * \param[in] timeout Timeout to use for request
+ *
+ * \return DBus reply
+ *
+ * \note If error is non-NULL, it is initialized, so the caller may always use
+ * dbus_error_is_set() to determine whether an error occurred; the caller
+ * is responsible for calling dbus_error_free() in this case.
+ */
+DBusMessage *
+pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection,
+ DBusError *error, int timeout)
+{
+ const char *method = NULL;
+ DBusMessage *reply = NULL;
+ DBusPendingCall* pending = NULL;
+
+ CRM_ASSERT(dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
+ method = dbus_message_get_member (msg);
+
+ /* Ensure caller can reliably check whether error is set */
+ if (error) {
+ dbus_error_init(error);
+ }
+
+ if (timeout <= 0) {
+ /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
+ timeout = DBUS_TIMEOUT_USE_DEFAULT;
+ }
+
+ // send message and get a handle for a reply
+ if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
+ if (error) {
+ dbus_set_error(error, ERR_SEND_FAILED,
+ "Could not queue DBus '%s' request", method);
+ }
+ return NULL;
+ }
+
+ dbus_connection_flush(connection);
+
+ if (pending) {
+ /* block until we receive a reply */
+ dbus_pending_call_block(pending);
+
+ /* get the reply message */
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ (void) pcmk_dbus_find_error(pending, reply, error);
+
+ if (pending) {
+ /* free the pending message handle */
+ dbus_pending_call_unref(pending);
+ }
+
+ return reply;
+}
+
+/*!
+ * \internal
+ * \brief Send a DBus message with a callback for the reply
+ *
+ * \param[in,out] msg DBus message to send
+ * \param[in,out] connection DBus connection to send on
+ * \param[in] done Function to call when pending call completes
+ * \param[in] user_data Data to pass to done callback
+ *
+ * \return Handle for reply on success, NULL on error
+ * \note The caller can assume that the done callback is called always and
+ * only when the return value is non-NULL. (This allows the caller to
+ * know where it should free dynamically allocated user_data.)
+ */
+DBusPendingCall *
+pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection,
+ void (*done)(DBusPendingCall *pending, void *user_data),
+ void *user_data, int timeout)
+{
+ const char *method = NULL;
+ DBusPendingCall* pending = NULL;
+
+ CRM_ASSERT(done);
+ CRM_ASSERT(dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
+ method = dbus_message_get_member(msg);
+
+ if (timeout <= 0) {
+ /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
+ timeout = DBUS_TIMEOUT_USE_DEFAULT;
+ }
+
+ // send message and get a handle for a reply
+ if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
+ crm_err("Could not send DBus %s message: failed", method);
+ return NULL;
+
+ } else if (pending == NULL) {
+ crm_err("Could not send DBus %s message: connection may be closed",
+ method);
+ return NULL;
+ }
+
+ if (dbus_pending_call_get_completed(pending)) {
+ crm_info("DBus %s message completed too soon", method);
+ /* Calling done() directly in this case instead of setting notify below
+ * breaks things
+ */
+ }
+ if (!dbus_pending_call_set_notify(pending, done, user_data, NULL)) {
+ return NULL;
+ }
+ return pending;
+}
+
+bool
+pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected,
+ const char *function, int line)
+{
+ int dtype = 0;
+ DBusMessageIter lfield;
+
+ if (field == NULL) {
+ if (dbus_message_iter_init(msg, &lfield)) {
+ field = &lfield;
+ }
+ }
+
+ if (field == NULL) {
+ do_crm_log_alias(LOG_INFO, __FILE__, function, line,
+ "DBus reply has empty parameter list (expected '%c')",
+ expected);
+ return FALSE;
+ }
+
+ dtype = dbus_message_iter_get_arg_type(field);
+
+ if (dtype != expected) {
+ DBusMessageIter args;
+ char *sig;
+
+ dbus_message_iter_init(msg, &args);
+ sig = dbus_message_iter_get_signature(&args);
+ do_crm_log_alias(LOG_INFO, __FILE__, function, line,
+ "DBus reply has unexpected type "
+ "(expected '%c' not '%c' in '%s')",
+ expected, dtype, sig);
+ dbus_free(sig);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Property queries
+ */
+
+/* DBus APIs often provide queryable properties that use this standard
+ * interface. See:
+ * https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
+ */
+#define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties"
+
+// Callback prototype for when a DBus property query result is received
+typedef void (*property_callback_func)(const char *name, // Property name
+ const char *value, // Property value
+ void *userdata); // Caller-provided data
+
+// Data needed by DBus property queries
+struct property_query {
+ char *name; // Property name being queried
+ char *target; // Name of DBus bus that query should be sent to
+ char *object; // DBus object path for object with the property
+ void *userdata; // Caller-provided data to supply to callback
+ property_callback_func callback; // Function to call when result is received
+};
+
+static void
+free_property_query(struct property_query *data)
+{
+ free(data->target);
+ free(data->object);
+ free(data->name);
+ free(data);
+}
+
+static char *
+handle_query_result(DBusMessage *reply, struct property_query *data)
+{
+ DBusError error;
+ char *output = NULL;
+ DBusMessageIter args;
+ DBusMessageIter variant_iter;
+ DBusBasicValue value;
+
+ // First, check if the reply contains an error
+ if (pcmk_dbus_find_error((void*)&error, reply, &error)) {
+ crm_err("DBus query for %s property '%s' failed: %s",
+ data->object, data->name, error.message);
+ dbus_error_free(&error);
+ goto cleanup;
+ }
+
+ // The lone output argument should be a DBus variant type
+ dbus_message_iter_init(reply, &args);
+ if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_VARIANT,
+ __func__, __LINE__)) {
+ crm_err("DBus query for %s property '%s' failed: Unexpected reply type",
+ data->object, data->name);
+ goto cleanup;
+ }
+
+ // The variant should be a string
+ dbus_message_iter_recurse(&args, &variant_iter);
+ if (!pcmk_dbus_type_check(reply, &variant_iter, DBUS_TYPE_STRING,
+ __func__, __LINE__)) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Unexpected variant type", data->object, data->name);
+ goto cleanup;
+ }
+ dbus_message_iter_get_basic(&variant_iter, &value);
+
+ // There should be no more arguments (in variant or reply)
+ dbus_message_iter_next(&variant_iter);
+ if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_INVALID) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Too many arguments in reply",
+ data->object, data->name);
+ goto cleanup;
+ }
+ dbus_message_iter_next(&args);
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_INVALID) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Too many arguments in reply", data->object, data->name);
+ goto cleanup;
+ }
+
+ crm_trace("DBus query result for %s: %s='%s'",
+ data->object, data->name, (value.str? value.str : ""));
+
+ if (data->callback) { // Query was asynchronous
+ data->callback(data->name, (value.str? value.str : ""), data->userdata);
+
+ } else { // Query was synchronous
+ output = strdup(value.str? value.str : "");
+ }
+
+ cleanup:
+ free_property_query(data);
+ return output;
+}
+
+static void
+async_query_result_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply = NULL;
+ char *value = NULL;
+
+ if (pending) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ value = handle_query_result(reply, user_data);
+ free(value);
+
+ if (reply) {
+ dbus_message_unref(reply);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Query a property on a DBus object
+ *
+ * \param[in,out] connection An active connection to DBus
+ * \param[in] target DBus name that the query should be sent to
+ * \param[in] obj DBus object path for object with the property
+ * \param[in] iface DBus interface for property to query
+ * \param[in] name Name of property to query
+ * \param[in] callback If not NULL, perform query asynchronously and call
+ * this function when query completes
+ * \param[in,out] userdata Caller-provided data to provide to \p callback
+ * \param[out] pending If \p callback is not NULL, this will be set to
+ * handle for the reply (or NULL on error)
+ * \param[in] timeout Abort query if it takes longer than this (ms)
+ *
+ * \return NULL if \p callback is non-NULL (i.e. asynchronous), otherwise a
+ * newly allocated string with property value
+ * \note It is the caller's responsibility to free the result with free().
+ */
+char *
+pcmk_dbus_get_property(DBusConnection *connection, const char *target,
+ const char *obj, const gchar * iface, const char *name,
+ property_callback_func callback, void *userdata,
+ DBusPendingCall **pending, int timeout)
+{
+ DBusMessage *msg;
+ char *output = NULL;
+ struct property_query *query_data = NULL;
+
+ CRM_CHECK((connection != NULL) && (target != NULL) && (obj != NULL)
+ && (iface != NULL) && (name != NULL), return NULL);
+
+ crm_trace("Querying DBus %s for %s property '%s'",
+ target, obj, name);
+
+ // Create a new message to use to invoke method
+ msg = dbus_message_new_method_call(target, obj, BUS_PROPERTY_IFACE, "Get");
+ if (msg == NULL) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Unable to create message", obj, name);
+ return NULL;
+ }
+
+ // Add the interface name and property name as message arguments
+ if (!dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &iface,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Could not append arguments", obj, name);
+ dbus_message_unref(msg);
+ return NULL;
+ }
+
+ query_data = malloc(sizeof(struct property_query));
+ if (query_data == NULL) {
+ crm_crit("DBus query for %s property '%s' failed: Out of memory",
+ obj, name);
+ dbus_message_unref(msg);
+ return NULL;
+ }
+
+ query_data->target = strdup(target);
+ query_data->object = strdup(obj);
+ query_data->callback = callback;
+ query_data->userdata = userdata;
+ query_data->name = strdup(name);
+ CRM_CHECK((query_data->target != NULL)
+ && (query_data->object != NULL)
+ && (query_data->name != NULL),
+ free_property_query(query_data);
+ dbus_message_unref(msg);
+ return NULL);
+
+ if (query_data->callback) { // Asynchronous
+ DBusPendingCall *local_pending;
+
+ local_pending = pcmk_dbus_send(msg, connection, async_query_result_cb,
+ query_data, timeout);
+ if (local_pending == NULL) {
+ // async_query_result_cb() was not called in this case
+ free_property_query(query_data);
+ query_data = NULL;
+ }
+
+ if (pending) {
+ *pending = local_pending;
+ }
+
+ } else { // Synchronous
+ DBusMessage *reply = pcmk_dbus_send_recv(msg, connection, NULL,
+ timeout);
+
+ output = handle_query_result(reply, query_data);
+
+ if (reply) {
+ dbus_message_unref(reply);
+ }
+ }
+
+ dbus_message_unref(msg);
+
+ return output;
+}