| /* libnih |
| * |
| * dbus_connection.c - D-Bus client, bus and server connection handling |
| * |
| * Copyright © 2009 Scott James Remnant <scott@netsplit.com>. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif /* HAVE_CONFIG_H */ |
| |
| |
| #include <dbus/dbus.h> |
| |
| #include <nih/macros.h> |
| #include <nih/alloc.h> |
| #include <nih/list.h> |
| #include <nih/timer.h> |
| #include <nih/io.h> |
| #include <nih/main.h> |
| #include <nih/logging.h> |
| #include <nih/error.h> |
| |
| #include <nih-dbus/dbus_error.h> |
| |
| #include "dbus_connection.h" |
| |
| |
| /* Prototypes for static functions */ |
| static dbus_bool_t nih_dbus_add_watch (DBusWatch *watch, |
| void *data); |
| static void nih_dbus_remove_watch (DBusWatch *watch, |
| void *data); |
| static void nih_dbus_watch_toggled (DBusWatch *watch, |
| void *data); |
| static void nih_dbus_watcher (DBusWatch *watch, |
| NihIoWatch *io_watch, |
| NihIoEvents events); |
| static dbus_bool_t nih_dbus_add_timeout (DBusTimeout *timeout, |
| void *data); |
| static void nih_dbus_remove_timeout (DBusTimeout *timeout, |
| void *data); |
| static void nih_dbus_timeout_toggled (DBusTimeout *timeout, |
| void *data); |
| static void nih_dbus_timer (DBusTimeout *timeout, |
| NihTimer *timer); |
| static void nih_dbus_wakeup_main (void *data); |
| static void nih_dbus_callback (DBusConnection *conn, |
| NihMainLoopFunc *loop); |
| static DBusHandlerResult nih_dbus_connection_disconnected (DBusConnection *conn, |
| DBusMessage *message, |
| NihDBusDisconnectHandler handler); |
| static void nih_dbus_new_connection (DBusServer *server, |
| DBusConnection *conn, |
| void *data); |
| |
| |
| /** |
| * main_loop_slot: |
| * |
| * Slot we use to store the main loop function in the connection. |
| **/ |
| static dbus_int32_t main_loop_slot = -1; |
| |
| /** |
| * connect_handler_slot: |
| * |
| * Slot we use to store the connection handler in the server. |
| **/ |
| static dbus_int32_t connect_handler_slot = -1; |
| |
| /** |
| * disconnect_handler_slot: |
| * |
| * Slot we use to store the disconnect handler in the server. |
| **/ |
| static dbus_int32_t disconnect_handler_slot = -1; |
| |
| |
| /** |
| * nih_dbus_connect: |
| * @address: address of D-Bus bus or server, |
| * @disconnect_handler: function to call on disconnection. |
| * |
| * Establishes a connection to the D-Bus bus or server at @address |
| * (specified in D-Bus's own address syntax) and sets up the connection |
| * within libnih's own main loop so that messages will be received, sent |
| * and dispatched automatically. |
| * |
| * The returned connection object IS NOT allocated with nih_alloc() and |
| * is instead allocated and managed by the D-Bus library, it may not be |
| * used as a context for other allocations. Instead you should use |
| * D-Bus data slots and free functions to attach other data to this. |
| * |
| * The connection object is shared and will persist as long as the |
| * server maintains the connection, you should not attempt to close or |
| * unreference the connection yourself. |
| * |
| * Returns: new D-Bus connection object or NULL on raised error. |
| **/ |
| DBusConnection * |
| nih_dbus_connect (const char *address, |
| NihDBusDisconnectHandler disconnect_handler) |
| { |
| DBusConnection *conn; |
| DBusError error; |
| |
| nih_assert (address != NULL); |
| |
| dbus_error_init (&error); |
| |
| conn = dbus_connection_open (address, &error); |
| if (! conn) { |
| nih_dbus_error_raise (error.name, error.message); |
| dbus_error_free (&error); |
| |
| return NULL; |
| } |
| |
| if (nih_dbus_setup (conn, disconnect_handler) < 0) { |
| dbus_connection_unref (conn); |
| |
| nih_return_no_memory_error (NULL); |
| } |
| |
| return conn; |
| } |
| |
| /** |
| * nih_dbus_bus: |
| * @bus: D-Bus bus type to connect to, |
| * @disconnect_handler: function to call on disconnection. |
| * |
| * Establishes a connection to the given D-Bus @bus and sets up |
| * the connection within libnih's own main loop so that messages will be |
| * received, sent and dispatched automatically. |
| * |
| * Unlike the ordinary D-Bus API, this connection will not cause the exit() |
| * function to be called should the bus go away. |
| * |
| * The returned connection object IS NOT allocated with nih_alloc() and |
| * is instead allocated and managed by the D-Bus library, it may not be |
| * used as a context for other allocations. Instead you should use |
| * D-Bus data slots and free functions to attach other data to this. |
| * |
| * The connection object is shared and will persist as long as the |
| * server maintains the connection, you should not attempt to close or |
| * unreference the connection yourself. |
| * |
| * Returns: new D-Bus connection object or NULL on raised error. |
| **/ |
| DBusConnection * |
| nih_dbus_bus (DBusBusType bus, |
| NihDBusDisconnectHandler disconnect_handler) |
| { |
| DBusConnection *conn; |
| DBusError error; |
| |
| dbus_error_init (&error); |
| |
| conn = dbus_bus_get (bus, &error); |
| if (! conn) { |
| nih_dbus_error_raise (error.name, error.message); |
| dbus_error_free (&error); |
| |
| return NULL; |
| } |
| |
| dbus_connection_set_exit_on_disconnect (conn, FALSE); |
| |
| if (nih_dbus_setup (conn, disconnect_handler) < 0) { |
| dbus_connection_unref (conn); |
| |
| nih_return_no_memory_error (NULL); |
| } |
| |
| return conn; |
| } |
| |
| /** |
| * nih_dbus_setup: |
| * @conn: D-Bus connection to setup, |
| * @disconnect_handler: function to call on disconnection. |
| * |
| * Sets up the given connection @conn so that it may use libnih's own |
| * main loop meaning that messages will be received, sent and dispatched |
| * automatically. |
| * |
| * This will also set up a handler for the disconnected signal that will |
| * automatically unreference the connection after calling the given |
| * @disconnect_handler. |
| * |
| * Returns: zero on success, negative value on insufficient memory. |
| **/ |
| int |
| nih_dbus_setup (DBusConnection *conn, |
| NihDBusDisconnectHandler disconnect_handler) |
| { |
| NihMainLoopFunc *loop; |
| |
| nih_assert (conn != NULL); |
| |
| /* Allocate a data slot for storing the main loop function; if |
| * this is set for the structure, we've already set it up before |
| * and this is being shared so we can skip down to just adding |
| * the new disconnect handler. |
| */ |
| if (! dbus_connection_allocate_data_slot (&main_loop_slot)) |
| return -1; |
| |
| if (dbus_connection_get_data (conn, main_loop_slot)) |
| goto shared; |
| |
| |
| /* Add the main loop function and store it in the data slot, |
| * this means it will be automatically freed. |
| */ |
| loop = nih_main_loop_add_func (NULL, (NihMainLoopCb)nih_dbus_callback, |
| conn); |
| if (! loop) |
| return -1; |
| |
| if (! dbus_connection_set_data (conn, main_loop_slot, loop, |
| (DBusFreeFunction)nih_discard)) { |
| nih_free (loop); |
| return -1; |
| } |
| |
| /* Allow the connection to watch its file descriptors */ |
| if (! dbus_connection_set_watch_functions (conn, |
| nih_dbus_add_watch, |
| nih_dbus_remove_watch, |
| nih_dbus_watch_toggled, |
| NULL, NULL)) |
| return -1; |
| |
| /* Allow the connection to set up timers */ |
| if (! dbus_connection_set_timeout_functions (conn, |
| nih_dbus_add_timeout, |
| nih_dbus_remove_timeout, |
| nih_dbus_timeout_toggled, |
| NULL, NULL)) |
| return -1; |
| |
| /* Allow the connection to wake up the main loop */ |
| dbus_connection_set_wakeup_main_function (conn, |
| nih_dbus_wakeup_main, |
| NULL, NULL); |
| |
| shared: |
| /* Add the filter for the disconnect handler (which may be NULL, |
| * but even then we have to unreference it). |
| */ |
| if (! dbus_connection_add_filter ( |
| conn, (DBusHandleMessageFunction)nih_dbus_connection_disconnected, |
| disconnect_handler, NULL)) |
| return -1; |
| |
| return 0; |
| } |
| |
| |
| /** |
| * nih_dbus_server: |
| * @address: intended address of D-Bus server, |
| * @connect_handler: function to call on new connections, |
| * @disconnect_handler: function to call on disconnection of connections. |
| * |
| * Creates a listening D-Bus server at @address (specified in D-Bus's own |
| * address syntax) and sets up the server within libnih's own main loop |
| * so that socket events will be handled automatically. |
| * |
| * New connections are accepted if the @connect_handler returns TRUE and |
| * they too set up within libnih's own main loop so that messages will be |
| * received, sent and dispatched. If those connections are disconnected, |
| * @disconnect_handler will be called for them and they will be |
| * automatically unreferenced. |
| * |
| * The returned server object and any created connection objects ARE NOT |
| * allocated with nih_alloc() and are instead allocated and managed by the |
| * D-Bus library, they may not be used as a context for other allocations. |
| * Instead you should use D-Bus data slots and free functions to attach |
| * other data to them. |
| * |
| * Both the server object and any created connection objects are private, |
| * you may close and unreference them when you are finished with them. |
| * |
| * Returns: new D-Bus server object or NULL on raised error. |
| **/ |
| DBusServer * |
| nih_dbus_server (const char *address, |
| NihDBusConnectHandler connect_handler, |
| NihDBusDisconnectHandler disconnect_handler) |
| { |
| DBusServer *server; |
| DBusError error; |
| |
| nih_assert (address != NULL); |
| |
| dbus_error_init (&error); |
| |
| server = dbus_server_listen (address, &error); |
| if (! server) { |
| nih_dbus_error_raise (error.name, error.message); |
| dbus_error_free (&error); |
| |
| return NULL; |
| } |
| |
| /* Allocate a slot to store the connect handler */ |
| if (! dbus_server_allocate_data_slot (&connect_handler_slot)) |
| goto error; |
| |
| if (! dbus_server_set_data (server, connect_handler_slot, |
| connect_handler, NULL)) |
| goto error; |
| |
| /* Allocate a slot to store the disconnect handler */ |
| if (! dbus_server_allocate_data_slot (&disconnect_handler_slot)) |
| goto error; |
| |
| if (! dbus_server_set_data (server, disconnect_handler_slot, |
| disconnect_handler, NULL)) |
| goto error; |
| |
| /* Allow the server to watch its file descriptors */ |
| if (! dbus_server_set_watch_functions (server, |
| nih_dbus_add_watch, |
| nih_dbus_remove_watch, |
| nih_dbus_watch_toggled, |
| NULL, NULL)) |
| goto error; |
| |
| /* Allow the server to set up timers */ |
| if (! dbus_server_set_timeout_functions (server, |
| nih_dbus_add_timeout, |
| nih_dbus_remove_timeout, |
| nih_dbus_timeout_toggled, |
| NULL, NULL)) |
| goto error; |
| |
| /* Set the function to be called for new connectoins */ |
| dbus_server_set_new_connection_function (server, |
| nih_dbus_new_connection, |
| NULL, NULL); |
| |
| return server; |
| |
| error: |
| dbus_server_unref (server); |
| |
| nih_return_no_memory_error (NULL); |
| } |
| |
| |
| /** |
| * nih_dbus_add_watch: |
| * @watch: D-Bus watch to be added, |
| * @data: not used. |
| * |
| * Called by D-Bus to register the given file descriptor @watch in our main |
| * loop; we create an NihIoWatch structure for it with events matching the |
| * watch's flags - even if the watch is not enabled (in which case we remove |
| * it from the watch list). |
| * |
| * The NihIoWatch is stored in the watch's data member. |
| * |
| * Returns: TRUE if the watch could be added, FALSE on insufficient memory. |
| **/ |
| static dbus_bool_t |
| nih_dbus_add_watch (DBusWatch *watch, |
| void *data) |
| { |
| NihIoWatch *io_watch; |
| int fd, flags; |
| NihIoEvents events = NIH_IO_EXCEPT; |
| |
| nih_assert (watch != NULL); |
| nih_assert (dbus_watch_get_data (watch) == NULL); |
| |
| fd = dbus_watch_get_unix_fd (watch); |
| nih_assert (fd >= 0); |
| |
| flags = dbus_watch_get_flags (watch); |
| if (flags & DBUS_WATCH_READABLE) |
| events |= NIH_IO_READ; |
| if (flags & DBUS_WATCH_WRITABLE) |
| events |= NIH_IO_WRITE; |
| |
| io_watch = nih_io_add_watch (NULL, fd, events, |
| (NihIoWatcher)nih_dbus_watcher, watch); |
| if (! io_watch) |
| return FALSE; |
| |
| dbus_watch_set_data (watch, io_watch, (DBusFreeFunction)nih_discard); |
| |
| if (! dbus_watch_get_enabled (watch)) |
| nih_list_remove (&io_watch->entry); |
| |
| return TRUE; |
| } |
| |
| /** |
| * nih_dbus_remove_watch: |
| * @watch: D-Bus watch to be removed, |
| * @data: not used. |
| * |
| * Called by D-Bus to unregister the given file descriptor @watch from our |
| * main loop; we take the NihIoWatch structure from the watch's data member |
| * and free it. |
| **/ |
| static void |
| nih_dbus_remove_watch (DBusWatch *watch, |
| void *data) |
| { |
| NihIoWatch *io_watch; |
| |
| nih_assert (watch != NULL); |
| |
| io_watch = dbus_watch_get_data (watch); |
| nih_assert (io_watch != NULL); |
| |
| /* Only remove it from the list, D-Bus will call nih_free for us |
| * when we set the data to NULL. |
| **/ |
| nih_list_remove (&io_watch->entry); |
| |
| dbus_watch_set_data (watch, NULL, NULL); |
| } |
| |
| /** |
| * nih_dbus_watch_toggled: |
| * @watch: D-Bus watch to be toggled, |
| * @data: not used. |
| * |
| * Called by D-Bus because the given file descriptor @watch has been enabled |
| * or disabled; we take the NihIoWatch structure from the watch's data member |
| * and either add it to or remove it from the watches list. |
| **/ |
| static void |
| nih_dbus_watch_toggled (DBusWatch *watch, |
| void *data) |
| { |
| NihIoWatch *io_watch; |
| |
| nih_assert (watch != NULL); |
| |
| io_watch = dbus_watch_get_data (watch); |
| nih_assert (io_watch != NULL); |
| |
| if (dbus_watch_get_enabled (watch)) { |
| nih_list_add (nih_io_watches, &io_watch->entry); |
| } else { |
| nih_list_remove (&io_watch->entry); |
| } |
| } |
| |
| /** |
| * nih_dbus_watcher: |
| * @watch: D-Bus watch event occurred for, |
| * @io_watch: NihIoWatch for which an event occurred, |
| * @events: events that occurred. |
| * |
| * Called because an event has occurred on @io_watch that we need to pass |
| * onto the underlying @watch. |
| **/ |
| static void |
| nih_dbus_watcher (DBusWatch *watch, |
| NihIoWatch *io_watch, |
| NihIoEvents events) |
| { |
| int flags = 0; |
| |
| nih_assert (watch != NULL); |
| nih_assert (io_watch != NULL); |
| |
| if (events & NIH_IO_READ) |
| flags |= DBUS_WATCH_READABLE; |
| if (events & NIH_IO_WRITE) |
| flags |= DBUS_WATCH_WRITABLE; |
| if (events & NIH_IO_EXCEPT) |
| flags |= DBUS_WATCH_ERROR; |
| |
| dbus_watch_handle (watch, flags); |
| } |
| |
| |
| /** |
| * nih_dbus_add_timeout: |
| * @timeout: D-Bus timeout to be added, |
| * @data: not used. |
| * |
| * Called by D-Bus to register the given @timeout in our main loop; we create |
| * a periodic NihTimer structure for it with the correct interval even if |
| * the timeout is not enabled (in which case we remove it from the timer |
| * list). |
| * |
| * The NihTimer is stored in the timeout's data member. |
| * |
| * Returns: TRUE if the timeout could be added, FALSE on insufficient memory. |
| **/ |
| static dbus_bool_t |
| nih_dbus_add_timeout (DBusTimeout *timeout, |
| void *data) |
| { |
| NihTimer *timer; |
| int interval; |
| |
| nih_assert (timeout != NULL); |
| nih_assert (dbus_timeout_get_data (timeout) == NULL); |
| |
| interval = dbus_timeout_get_interval (timeout); |
| |
| timer = nih_timer_add_periodic (NULL, (interval - 1) / 1000 + 1, |
| (NihTimerCb)nih_dbus_timer, timeout); |
| if (! timer) |
| return FALSE; |
| |
| dbus_timeout_set_data (timeout, timer, (DBusFreeFunction)nih_discard); |
| |
| if (! dbus_timeout_get_enabled (timeout)) |
| nih_list_remove (&timer->entry); |
| |
| return TRUE; |
| } |
| |
| /** |
| * nih_dbus_remove_timeout: |
| * @timeout: D-Bus timeout to be removed, |
| * @data: not used. |
| * |
| * Called by D-Bus to unregister the given @timeout from our main loop; we |
| * take the NihTimer structure from the timeout's data member and free it. |
| **/ |
| static void |
| nih_dbus_remove_timeout (DBusTimeout *timeout, |
| void *data) |
| { |
| NihTimer *timer; |
| |
| nih_assert (timeout != NULL); |
| |
| timer = dbus_timeout_get_data (timeout); |
| nih_assert (timer != NULL); |
| |
| /* Only remove it from the list, D-Bus will call nih_free for us |
| * when we set the data to NULL. |
| */ |
| nih_list_remove (&timer->entry); |
| |
| dbus_timeout_set_data (timeout, NULL, NULL); |
| } |
| |
| /** |
| * nih_dbus_timeout_toggled: |
| * @timeout: D-Bus timeout to be toggled, |
| * @data: not used. |
| * |
| * Called by D-Bus because the @timeout has been enabled or disabled; we |
| * take the NihTimer structure from the timeout's data member and either |
| * add it to or remove it from the timers list. |
| **/ |
| static void |
| nih_dbus_timeout_toggled (DBusTimeout *timeout, |
| void *data) |
| { |
| NihTimer *timer; |
| int interval; |
| |
| nih_assert (timeout != NULL); |
| |
| timer = dbus_timeout_get_data (timeout); |
| nih_assert (timer != NULL); |
| |
| if (dbus_timeout_get_enabled (timeout)) { |
| nih_list_add (nih_timers, &timer->entry); |
| } else { |
| nih_list_remove (&timer->entry); |
| } |
| |
| /* D-Bus may toggle the timer in an attempt to change the timeout */ |
| interval = dbus_timeout_get_interval (timeout); |
| |
| timer->period = (interval - 1) / 1000 + 1; |
| timer->due = time (NULL) + timer->period; |
| } |
| |
| /** |
| * nih_dbus_timer: |
| * @timeout: D-Bus timeout event occurred for, |
| * @timer: timer that triggered the call. |
| * |
| * Called because @timer has elapsed and we need to pass that onto the |
| * underlying @timeout. |
| **/ |
| static void |
| nih_dbus_timer (DBusTimeout *timeout, |
| NihTimer *timer) |
| { |
| nih_assert (timeout != NULL); |
| nih_assert (timer != NULL); |
| |
| dbus_timeout_handle (timeout); |
| } |
| |
| |
| /** |
| * nih_dbus_wakeup_main: |
| * @data: not used. |
| * |
| * Called by D-Bus to wakeup the main loop. |
| **/ |
| static void |
| nih_dbus_wakeup_main (void *data) |
| { |
| nih_main_loop_interrupt (); |
| } |
| |
| /** |
| * nih_dbus_callback: |
| * @conn: D-Bus connection, |
| * @loop: loop callback structure. |
| * |
| * Called on each iteration of our main loop to dispatch any remaining items |
| * of data from the given D-Bus connection @conn so that messages will be |
| * handled automatically. |
| **/ |
| static void |
| nih_dbus_callback (DBusConnection *conn, |
| NihMainLoopFunc *loop) |
| { |
| nih_assert (conn != NULL); |
| nih_assert (loop != NULL); |
| |
| while (dbus_connection_dispatch (conn) == DBUS_DISPATCH_DATA_REMAINS) |
| ; |
| } |
| |
| |
| /** |
| * nih_dbus_connection_disconnected: |
| * @conn: D-Bus connection, |
| * @message: D-Bus message received, |
| * @handler: Disconnection handler. |
| * |
| * Called as a filter function to determine whether @conn has been |
| * disconnected, and if so, call the user disconnect @handler function. |
| * |
| * Once the handler has been called, the connection will be automatically |
| * unreferenced. |
| * |
| * Returns: result of handling the message. |
| **/ |
| static DBusHandlerResult |
| nih_dbus_connection_disconnected (DBusConnection *conn, |
| DBusMessage *message, |
| NihDBusDisconnectHandler handler) |
| { |
| nih_assert (conn != NULL); |
| nih_assert (message != NULL); |
| |
| if (! dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, |
| "Disconnected")) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| if (! dbus_message_has_path (message, DBUS_PATH_LOCAL)) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| /* Ok, it's really the disconnected signal, call the handler. */ |
| if (handler) |
| handler (conn); |
| |
| dbus_connection_unref (conn); |
| |
| /* Lie. We want other filter functions for this to be called so |
| * we unreference for each copy we hold. |
| */ |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /** |
| * nih_dbus_new_connection: |
| * @server: D-Bus server, |
| * @conn: new D-Bus connection, |
| * @data: not used. |
| * |
| * Called by D-Bus because a new connection @conn has been made to @server; |
| * we call the connect handler if set, and if that returns TRUE (or not set), |
| * we reference the connection so it is not dropped and set it up with |
| * our main loop. |
| **/ |
| static void |
| nih_dbus_new_connection (DBusServer *server, |
| DBusConnection *conn, |
| void *data) |
| { |
| NihDBusConnectHandler connect_handler; |
| NihDBusDisconnectHandler disconnect_handler; |
| |
| nih_assert (server != NULL); |
| nih_assert (conn != NULL); |
| |
| /* Call the connect handler if set, if it returns FALSE, drop the |
| * connection. |
| */ |
| connect_handler = dbus_server_get_data (server, connect_handler_slot); |
| if (connect_handler && (! connect_handler (server, conn))) |
| return; |
| |
| /* We're keeping the connection, reference it and hook it up to the |
| * main loop. |
| */ |
| dbus_connection_ref (conn); |
| disconnect_handler = dbus_server_get_data (server, |
| disconnect_handler_slot); |
| NIH_ZERO (nih_dbus_setup (conn, disconnect_handler)); |
| } |