LCOV - code coverage report
Current view: top level - services - dbus.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 285 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 24 0.0 %

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

Generated by: LCOV version 1.14