| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* dbus-server.c DBusServer object |
| * |
| * Copyright (C) 2002, 2003, 2004, 2005 Red Hat Inc. |
| * |
| * Licensed under the Academic Free License version 2.1 |
| * |
| * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <config.h> |
| #include "dbus-server.h" |
| #include "dbus-server-unix.h" |
| #include "dbus-server-socket.h" |
| #include "dbus-string.h" |
| #ifdef DBUS_BUILD_TESTS |
| #include "dbus-server-debug-pipe.h" |
| #endif |
| #include "dbus-address.h" |
| #include "dbus-protocol.h" |
| |
| /** |
| * @defgroup DBusServer DBusServer |
| * @ingroup DBus |
| * @brief Server that listens for new connections. |
| * |
| * A DBusServer represents a server that other applications |
| * can connect to. Each connection from another application |
| * is represented by a #DBusConnection. |
| * |
| * @todo Thread safety hasn't been tested much for #DBusServer |
| * @todo Need notification to apps of disconnection, may matter for some transports |
| */ |
| |
| /** |
| * @defgroup DBusServerInternals DBusServer implementation details |
| * @ingroup DBusInternals |
| * @brief Implementation details of DBusServer |
| * |
| * @{ |
| */ |
| |
| /* this is a little fragile since it assumes the address doesn't |
| * already have a guid, but it shouldn't |
| */ |
| static char* |
| copy_address_with_guid_appended (const DBusString *address, |
| const DBusString *guid_hex) |
| { |
| DBusString with_guid; |
| char *retval; |
| |
| if (!_dbus_string_init (&with_guid)) |
| return NULL; |
| |
| if (!_dbus_string_copy (address, 0, &with_guid, |
| _dbus_string_get_length (&with_guid)) || |
| !_dbus_string_append (&with_guid, ",guid=") || |
| !_dbus_string_copy (guid_hex, 0, |
| &with_guid, _dbus_string_get_length (&with_guid))) |
| { |
| _dbus_string_free (&with_guid); |
| return NULL; |
| } |
| |
| retval = NULL; |
| _dbus_string_steal_data (&with_guid, &retval); |
| |
| _dbus_string_free (&with_guid); |
| |
| return retval; /* may be NULL if steal_data failed */ |
| } |
| |
| /** |
| * Initializes the members of the DBusServer base class. |
| * Chained up to by subclass constructors. |
| * |
| * @param server the server. |
| * @param vtable the vtable for the subclass. |
| * @param address the server's address |
| * @returns #TRUE on success. |
| */ |
| dbus_bool_t |
| _dbus_server_init_base (DBusServer *server, |
| const DBusServerVTable *vtable, |
| const DBusString *address) |
| { |
| server->vtable = vtable; |
| server->refcount.value = 1; |
| |
| server->address = NULL; |
| server->watches = NULL; |
| server->timeouts = NULL; |
| |
| if (!_dbus_string_init (&server->guid_hex)) |
| return FALSE; |
| |
| _dbus_generate_uuid (&server->guid); |
| |
| if (!_dbus_uuid_encode (&server->guid, &server->guid_hex)) |
| goto failed; |
| |
| server->address = copy_address_with_guid_appended (address, |
| &server->guid_hex); |
| if (server->address == NULL) |
| goto failed; |
| |
| _dbus_mutex_new_at_location (&server->mutex); |
| if (server->mutex == NULL) |
| goto failed; |
| |
| server->watches = _dbus_watch_list_new (); |
| if (server->watches == NULL) |
| goto failed; |
| |
| server->timeouts = _dbus_timeout_list_new (); |
| if (server->timeouts == NULL) |
| goto failed; |
| |
| _dbus_data_slot_list_init (&server->slot_list); |
| |
| _dbus_verbose ("Initialized server on address %s\n", server->address); |
| |
| return TRUE; |
| |
| failed: |
| _dbus_mutex_free_at_location (&server->mutex); |
| server->mutex = NULL; |
| if (server->watches) |
| { |
| _dbus_watch_list_free (server->watches); |
| server->watches = NULL; |
| } |
| if (server->timeouts) |
| { |
| _dbus_timeout_list_free (server->timeouts); |
| server->timeouts = NULL; |
| } |
| if (server->address) |
| { |
| dbus_free (server->address); |
| server->address = NULL; |
| } |
| _dbus_string_free (&server->guid_hex); |
| |
| return FALSE; |
| } |
| |
| /** |
| * Finalizes the members of the DBusServer base class. |
| * Chained up to by subclass finalizers. |
| * |
| * @param server the server. |
| */ |
| void |
| _dbus_server_finalize_base (DBusServer *server) |
| { |
| /* We don't have the lock, but nobody should be accessing |
| * concurrently since they don't have a ref |
| */ |
| #ifndef DBUS_DISABLE_CHECKS |
| _dbus_assert (!server->have_server_lock); |
| #endif |
| _dbus_assert (server->disconnected); |
| |
| /* calls out to application code... */ |
| _dbus_data_slot_list_free (&server->slot_list); |
| |
| dbus_server_set_new_connection_function (server, NULL, NULL, NULL); |
| |
| _dbus_watch_list_free (server->watches); |
| _dbus_timeout_list_free (server->timeouts); |
| |
| _dbus_mutex_free_at_location (&server->mutex); |
| |
| dbus_free (server->address); |
| |
| dbus_free_string_array (server->auth_mechanisms); |
| |
| _dbus_string_free (&server->guid_hex); |
| } |
| |
| |
| /** Function to be called in protected_change_watch() with refcount held */ |
| typedef dbus_bool_t (* DBusWatchAddFunction) (DBusWatchList *list, |
| DBusWatch *watch); |
| /** Function to be called in protected_change_watch() with refcount held */ |
| typedef void (* DBusWatchRemoveFunction) (DBusWatchList *list, |
| DBusWatch *watch); |
| /** Function to be called in protected_change_watch() with refcount held */ |
| typedef void (* DBusWatchToggleFunction) (DBusWatchList *list, |
| DBusWatch *watch, |
| dbus_bool_t enabled); |
| |
| static dbus_bool_t |
| protected_change_watch (DBusServer *server, |
| DBusWatch *watch, |
| DBusWatchAddFunction add_function, |
| DBusWatchRemoveFunction remove_function, |
| DBusWatchToggleFunction toggle_function, |
| dbus_bool_t enabled) |
| { |
| DBusWatchList *watches; |
| dbus_bool_t retval; |
| |
| HAVE_LOCK_CHECK (server); |
| |
| /* This isn't really safe or reasonable; a better pattern is the "do |
| * everything, then drop lock and call out" one; but it has to be |
| * propagated up through all callers |
| */ |
| |
| watches = server->watches; |
| if (watches) |
| { |
| server->watches = NULL; |
| _dbus_server_ref_unlocked (server); |
| SERVER_UNLOCK (server); |
| |
| if (add_function) |
| retval = (* add_function) (watches, watch); |
| else if (remove_function) |
| { |
| retval = TRUE; |
| (* remove_function) (watches, watch); |
| } |
| else |
| { |
| retval = TRUE; |
| (* toggle_function) (watches, watch, enabled); |
| } |
| |
| SERVER_LOCK (server); |
| server->watches = watches; |
| _dbus_server_unref_unlocked (server); |
| |
| return retval; |
| } |
| else |
| return FALSE; |
| } |
| |
| /** |
| * Adds a watch for this server, chaining out to application-provided |
| * watch handlers. |
| * |
| * @param server the server. |
| * @param watch the watch to add. |
| */ |
| dbus_bool_t |
| _dbus_server_add_watch (DBusServer *server, |
| DBusWatch *watch) |
| { |
| HAVE_LOCK_CHECK (server); |
| return protected_change_watch (server, watch, |
| _dbus_watch_list_add_watch, |
| NULL, NULL, FALSE); |
| } |
| |
| /** |
| * Removes a watch previously added with _dbus_server_remove_watch(). |
| * |
| * @param server the server. |
| * @param watch the watch to remove. |
| */ |
| void |
| _dbus_server_remove_watch (DBusServer *server, |
| DBusWatch *watch) |
| { |
| HAVE_LOCK_CHECK (server); |
| protected_change_watch (server, watch, |
| NULL, |
| _dbus_watch_list_remove_watch, |
| NULL, FALSE); |
| } |
| |
| /** |
| * Toggles a watch and notifies app via server's |
| * DBusWatchToggledFunction if available. It's an error to call this |
| * function on a watch that was not previously added. |
| * |
| * @param server the server. |
| * @param watch the watch to toggle. |
| * @param enabled whether to enable or disable |
| */ |
| void |
| _dbus_server_toggle_watch (DBusServer *server, |
| DBusWatch *watch, |
| dbus_bool_t enabled) |
| { |
| _dbus_assert (watch != NULL); |
| |
| HAVE_LOCK_CHECK (server); |
| protected_change_watch (server, watch, |
| NULL, NULL, |
| _dbus_watch_list_toggle_watch, |
| enabled); |
| } |
| |
| /** Function to be called in protected_change_timeout() with refcount held */ |
| typedef dbus_bool_t (* DBusTimeoutAddFunction) (DBusTimeoutList *list, |
| DBusTimeout *timeout); |
| /** Function to be called in protected_change_timeout() with refcount held */ |
| typedef void (* DBusTimeoutRemoveFunction) (DBusTimeoutList *list, |
| DBusTimeout *timeout); |
| /** Function to be called in protected_change_timeout() with refcount held */ |
| typedef void (* DBusTimeoutToggleFunction) (DBusTimeoutList *list, |
| DBusTimeout *timeout, |
| dbus_bool_t enabled); |
| |
| |
| static dbus_bool_t |
| protected_change_timeout (DBusServer *server, |
| DBusTimeout *timeout, |
| DBusTimeoutAddFunction add_function, |
| DBusTimeoutRemoveFunction remove_function, |
| DBusTimeoutToggleFunction toggle_function, |
| dbus_bool_t enabled) |
| { |
| DBusTimeoutList *timeouts; |
| dbus_bool_t retval; |
| |
| HAVE_LOCK_CHECK (server); |
| |
| /* This isn't really safe or reasonable; a better pattern is the "do everything, then |
| * drop lock and call out" one; but it has to be propagated up through all callers |
| */ |
| |
| timeouts = server->timeouts; |
| if (timeouts) |
| { |
| server->timeouts = NULL; |
| _dbus_server_ref_unlocked (server); |
| SERVER_UNLOCK (server); |
| |
| if (add_function) |
| retval = (* add_function) (timeouts, timeout); |
| else if (remove_function) |
| { |
| retval = TRUE; |
| (* remove_function) (timeouts, timeout); |
| } |
| else |
| { |
| retval = TRUE; |
| (* toggle_function) (timeouts, timeout, enabled); |
| } |
| |
| SERVER_LOCK (server); |
| server->timeouts = timeouts; |
| _dbus_server_unref_unlocked (server); |
| |
| return retval; |
| } |
| else |
| return FALSE; |
| } |
| |
| /** |
| * Adds a timeout for this server, chaining out to |
| * application-provided timeout handlers. The timeout should be |
| * repeatedly handled with dbus_timeout_handle() at its given interval |
| * until it is removed. |
| * |
| * @param server the server. |
| * @param timeout the timeout to add. |
| */ |
| dbus_bool_t |
| _dbus_server_add_timeout (DBusServer *server, |
| DBusTimeout *timeout) |
| { |
| return protected_change_timeout (server, timeout, |
| _dbus_timeout_list_add_timeout, |
| NULL, NULL, FALSE); |
| } |
| |
| /** |
| * Removes a timeout previously added with _dbus_server_add_timeout(). |
| * |
| * @param server the server. |
| * @param timeout the timeout to remove. |
| */ |
| void |
| _dbus_server_remove_timeout (DBusServer *server, |
| DBusTimeout *timeout) |
| { |
| protected_change_timeout (server, timeout, |
| NULL, |
| _dbus_timeout_list_remove_timeout, |
| NULL, FALSE); |
| } |
| |
| /** |
| * Toggles a timeout and notifies app via server's |
| * DBusTimeoutToggledFunction if available. It's an error to call this |
| * function on a timeout that was not previously added. |
| * |
| * @param server the server. |
| * @param timeout the timeout to toggle. |
| * @param enabled whether to enable or disable |
| */ |
| void |
| _dbus_server_toggle_timeout (DBusServer *server, |
| DBusTimeout *timeout, |
| dbus_bool_t enabled) |
| { |
| protected_change_timeout (server, timeout, |
| NULL, NULL, |
| _dbus_timeout_list_toggle_timeout, |
| enabled); |
| } |
| |
| |
| /** |
| * Like dbus_server_ref() but does not acquire the lock (must already be held) |
| * |
| * @param server the server. |
| */ |
| void |
| _dbus_server_ref_unlocked (DBusServer *server) |
| { |
| _dbus_assert (server != NULL); |
| _dbus_assert (server->refcount.value > 0); |
| |
| HAVE_LOCK_CHECK (server); |
| |
| #ifdef DBUS_HAVE_ATOMIC_INT |
| _dbus_atomic_inc (&server->refcount); |
| #else |
| _dbus_assert (server->refcount.value > 0); |
| |
| server->refcount.value += 1; |
| #endif |
| } |
| |
| /** |
| * Like dbus_server_unref() but does not acquire the lock (must already be held) |
| * |
| * @param server the server. |
| */ |
| void |
| _dbus_server_unref_unlocked (DBusServer *server) |
| { |
| dbus_bool_t last_unref; |
| |
| /* Keep this in sync with dbus_server_unref */ |
| |
| _dbus_assert (server != NULL); |
| _dbus_assert (server->refcount.value > 0); |
| |
| HAVE_LOCK_CHECK (server); |
| |
| #ifdef DBUS_HAVE_ATOMIC_INT |
| last_unref = (_dbus_atomic_dec (&server->refcount) == 1); |
| #else |
| _dbus_assert (server->refcount.value > 0); |
| |
| server->refcount.value -= 1; |
| last_unref = (server->refcount.value == 0); |
| #endif |
| |
| if (last_unref) |
| { |
| _dbus_assert (server->disconnected); |
| |
| SERVER_UNLOCK (server); |
| |
| _dbus_assert (server->vtable->finalize != NULL); |
| |
| (* server->vtable->finalize) (server); |
| } |
| } |
| |
| /** @} */ |
| |
| /** |
| * @addtogroup DBusServer |
| * |
| * @{ |
| */ |
| |
| |
| /** |
| * @typedef DBusServer |
| * |
| * An opaque object representing a server that listens for |
| * connections from other applications. Each time a connection |
| * is made, a new DBusConnection is created and made available |
| * via an application-provided DBusNewConnectionFunction. |
| * The DBusNewConnectionFunction is provided with |
| * dbus_server_set_new_connection_function(). |
| * |
| */ |
| |
| static const struct { |
| DBusServerListenResult (* func) (DBusAddressEntry *entry, |
| DBusServer **server_p, |
| DBusError *error); |
| } listen_funcs[] = { |
| { _dbus_server_listen_socket } |
| , { _dbus_server_listen_platform_specific } |
| #ifdef DBUS_BUILD_TESTS |
| , { _dbus_server_listen_debug_pipe } |
| #endif |
| }; |
| |
| /** |
| * Listens for new connections on the given address. If there are |
| * multiple semicolon-separated address entries in the address, tries |
| * each one and listens on the first one that works. |
| * |
| * Returns #NULL and sets error if listening fails for any reason. |
| * Otherwise returns a new #DBusServer. |
| * dbus_server_set_new_connection_function(), |
| * dbus_server_set_watch_functions(), and |
| * dbus_server_set_timeout_functions() should be called immediately to |
| * render the server fully functional. |
| * |
| * To free the server, applications must call first |
| * dbus_server_disconnect() and then dbus_server_unref(). |
| * |
| * @param address the address of this server. |
| * @param error location to store reason for failure. |
| * @returns a new #DBusServer, or #NULL on failure. |
| * |
| */ |
| DBusServer* |
| dbus_server_listen (const char *address, |
| DBusError *error) |
| { |
| DBusServer *server; |
| DBusAddressEntry **entries; |
| int len, i; |
| DBusError first_connect_error = DBUS_ERROR_INIT; |
| dbus_bool_t handled_once; |
| |
| _dbus_return_val_if_fail (address != NULL, NULL); |
| _dbus_return_val_if_error_is_set (error, NULL); |
| |
| if (!dbus_parse_address (address, &entries, &len, error)) |
| return NULL; |
| |
| server = NULL; |
| handled_once = FALSE; |
| |
| for (i = 0; i < len; i++) |
| { |
| int j; |
| |
| for (j = 0; j < (int) _DBUS_N_ELEMENTS (listen_funcs); ++j) |
| { |
| DBusServerListenResult result; |
| DBusError tmp_error = DBUS_ERROR_INIT; |
| |
| result = (* listen_funcs[j].func) (entries[i], |
| &server, |
| &tmp_error); |
| |
| if (result == DBUS_SERVER_LISTEN_OK) |
| { |
| _dbus_assert (server != NULL); |
| _DBUS_ASSERT_ERROR_CONTENT_IS_CLEAR (&tmp_error); |
| handled_once = TRUE; |
| goto out; |
| } |
| else if (result == DBUS_SERVER_LISTEN_BAD_ADDRESS) |
| { |
| _dbus_assert (server == NULL); |
| _DBUS_ASSERT_ERROR_CONTENT_IS_SET (&tmp_error); |
| dbus_move_error (&tmp_error, error); |
| handled_once = TRUE; |
| goto out; |
| } |
| else if (result == DBUS_SERVER_LISTEN_NOT_HANDLED) |
| { |
| _dbus_assert (server == NULL); |
| _DBUS_ASSERT_ERROR_CONTENT_IS_CLEAR (&tmp_error); |
| |
| /* keep trying addresses */ |
| } |
| else if (result == DBUS_SERVER_LISTEN_DID_NOT_CONNECT) |
| { |
| _dbus_assert (server == NULL); |
| _DBUS_ASSERT_ERROR_CONTENT_IS_SET (&tmp_error); |
| if (!dbus_error_is_set (&first_connect_error)) |
| dbus_move_error (&tmp_error, &first_connect_error); |
| else |
| dbus_error_free (&tmp_error); |
| |
| handled_once = TRUE; |
| |
| /* keep trying addresses */ |
| } |
| } |
| |
| _dbus_assert (server == NULL); |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| } |
| |
| out: |
| |
| if (!handled_once) |
| { |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| if (len > 0) |
| dbus_set_error (error, |
| DBUS_ERROR_BAD_ADDRESS, |
| "Unknown address type '%s'", |
| dbus_address_entry_get_method (entries[0])); |
| else |
| dbus_set_error (error, |
| DBUS_ERROR_BAD_ADDRESS, |
| "Empty address '%s'", |
| address); |
| } |
| |
| dbus_address_entries_free (entries); |
| |
| if (server == NULL) |
| { |
| _dbus_assert (error == NULL || dbus_error_is_set (&first_connect_error) || |
| dbus_error_is_set (error)); |
| |
| if (error && dbus_error_is_set (error)) |
| { |
| /* already set the error */ |
| } |
| else |
| { |
| /* didn't set the error but either error should be |
| * NULL or first_connect_error should be set. |
| */ |
| _dbus_assert (error == NULL || dbus_error_is_set (&first_connect_error)); |
| dbus_move_error (&first_connect_error, error); |
| } |
| |
| _DBUS_ASSERT_ERROR_CONTENT_IS_CLEAR (&first_connect_error); /* be sure we freed it */ |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| |
| return NULL; |
| } |
| else |
| { |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| return server; |
| } |
| } |
| |
| /** |
| * Increments the reference count of a DBusServer. |
| * |
| * @param server the server. |
| * @returns the server |
| */ |
| DBusServer * |
| dbus_server_ref (DBusServer *server) |
| { |
| _dbus_return_val_if_fail (server != NULL, NULL); |
| _dbus_return_val_if_fail (server->refcount.value > 0, NULL); |
| |
| #ifdef DBUS_HAVE_ATOMIC_INT |
| _dbus_atomic_inc (&server->refcount); |
| #else |
| SERVER_LOCK (server); |
| _dbus_assert (server->refcount.value > 0); |
| |
| server->refcount.value += 1; |
| SERVER_UNLOCK (server); |
| #endif |
| |
| return server; |
| } |
| |
| /** |
| * Decrements the reference count of a DBusServer. Finalizes the |
| * server if the reference count reaches zero. |
| * |
| * The server must be disconnected before the refcount reaches zero. |
| * |
| * @param server the server. |
| */ |
| void |
| dbus_server_unref (DBusServer *server) |
| { |
| dbus_bool_t last_unref; |
| |
| /* keep this in sync with unref_unlocked */ |
| |
| _dbus_return_if_fail (server != NULL); |
| _dbus_return_if_fail (server->refcount.value > 0); |
| |
| #ifdef DBUS_HAVE_ATOMIC_INT |
| last_unref = (_dbus_atomic_dec (&server->refcount) == 1); |
| #else |
| SERVER_LOCK (server); |
| |
| _dbus_assert (server->refcount.value > 0); |
| |
| server->refcount.value -= 1; |
| last_unref = (server->refcount.value == 0); |
| |
| SERVER_UNLOCK (server); |
| #endif |
| |
| if (last_unref) |
| { |
| /* lock not held! */ |
| _dbus_assert (server->disconnected); |
| |
| _dbus_assert (server->vtable->finalize != NULL); |
| |
| (* server->vtable->finalize) (server); |
| } |
| } |
| |
| /** |
| * Releases the server's address and stops listening for |
| * new clients. If called more than once, only the first |
| * call has an effect. Does not modify the server's |
| * reference count. |
| * |
| * @param server the server. |
| */ |
| void |
| dbus_server_disconnect (DBusServer *server) |
| { |
| _dbus_return_if_fail (server != NULL); |
| _dbus_return_if_fail (server->refcount.value > 0); |
| |
| SERVER_LOCK (server); |
| _dbus_server_ref_unlocked (server); |
| |
| _dbus_assert (server->vtable->disconnect != NULL); |
| |
| if (!server->disconnected) |
| { |
| /* this has to be first so recursive calls to disconnect don't happen */ |
| server->disconnected = TRUE; |
| |
| (* server->vtable->disconnect) (server); |
| } |
| |
| SERVER_UNLOCK (server); |
| dbus_server_unref (server); |
| } |
| |
| /** |
| * Returns #TRUE if the server is still listening for new connections. |
| * |
| * @param server the server. |
| */ |
| dbus_bool_t |
| dbus_server_get_is_connected (DBusServer *server) |
| { |
| dbus_bool_t retval; |
| |
| _dbus_return_val_if_fail (server != NULL, FALSE); |
| |
| SERVER_LOCK (server); |
| retval = !server->disconnected; |
| SERVER_UNLOCK (server); |
| |
| return retval; |
| } |
| |
| /** |
| * Returns the address of the server, as a newly-allocated |
| * string which must be freed by the caller. |
| * |
| * @param server the server |
| * @returns the address or #NULL if no memory |
| */ |
| char* |
| dbus_server_get_address (DBusServer *server) |
| { |
| char *retval; |
| |
| _dbus_return_val_if_fail (server != NULL, NULL); |
| |
| SERVER_LOCK (server); |
| retval = _dbus_strdup (server->address); |
| SERVER_UNLOCK (server); |
| |
| return retval; |
| } |
| |
| /** |
| * Returns the unique ID of the server, as a newly-allocated |
| * string which must be freed by the caller. This ID is |
| * normally used by clients to tell when two #DBusConnection |
| * would be equivalent (because the server address passed |
| * to dbus_connection_open() will have the same guid in the |
| * two cases). dbus_connection_open() can re-use an existing |
| * connection with the same ID instead of opening a new |
| * connection. |
| * |
| * This is an ID unique to each #DBusServer. Remember that |
| * a #DBusServer represents only one mode of connecting, |
| * so e.g. a bus daemon can listen on multiple addresses |
| * which will mean it has multiple #DBusServer each with |
| * their own ID. |
| * |
| * The ID is not a UUID in the sense of RFC4122; the details |
| * are explained in the D-Bus specification. |
| * |
| * @param server the server |
| * @returns the id of the server or #NULL if no memory |
| */ |
| char* |
| dbus_server_get_id (DBusServer *server) |
| { |
| char *retval; |
| |
| _dbus_return_val_if_fail (server != NULL, NULL); |
| |
| SERVER_LOCK (server); |
| retval = NULL; |
| _dbus_string_copy_data (&server->guid_hex, &retval); |
| SERVER_UNLOCK (server); |
| |
| return retval; |
| } |
| |
| /** |
| * Sets a function to be used for handling new connections. The given |
| * function is passed each new connection as the connection is |
| * created. If the new connection function increments the connection's |
| * reference count, the connection will stay alive. Otherwise, the |
| * connection will be unreferenced and closed. The new connection |
| * function may also close the connection itself, which is considered |
| * good form if the connection is not wanted. |
| * |
| * The connection here is private in the sense of |
| * dbus_connection_open_private(), so if the new connection function |
| * keeps a reference it must arrange for the connection to be closed. |
| * i.e. libdbus does not own this connection once the new connection |
| * function takes a reference. |
| * |
| * @param server the server. |
| * @param function a function to handle new connections. |
| * @param data data to pass to the new connection handler. |
| * @param free_data_function function to free the data. |
| */ |
| void |
| dbus_server_set_new_connection_function (DBusServer *server, |
| DBusNewConnectionFunction function, |
| void *data, |
| DBusFreeFunction free_data_function) |
| { |
| DBusFreeFunction old_free_function; |
| void *old_data; |
| |
| _dbus_return_if_fail (server != NULL); |
| |
| SERVER_LOCK (server); |
| old_free_function = server->new_connection_free_data_function; |
| old_data = server->new_connection_data; |
| |
| server->new_connection_function = function; |
| server->new_connection_data = data; |
| server->new_connection_free_data_function = free_data_function; |
| SERVER_UNLOCK (server); |
| |
| if (old_free_function != NULL) |
| (* old_free_function) (old_data); |
| } |
| |
| /** |
| * Sets the watch functions for the server. These functions are |
| * responsible for making the application's main loop aware of file |
| * descriptors that need to be monitored for events. |
| * |
| * This function behaves exactly like dbus_connection_set_watch_functions(); |
| * see the documentation for that routine. |
| * |
| * @param server the server. |
| * @param add_function function to begin monitoring a new descriptor. |
| * @param remove_function function to stop monitoring a descriptor. |
| * @param toggled_function function to notify when the watch is enabled/disabled |
| * @param data data to pass to add_function and remove_function. |
| * @param free_data_function function to be called to free the data. |
| * @returns #FALSE on failure (no memory) |
| */ |
| dbus_bool_t |
| dbus_server_set_watch_functions (DBusServer *server, |
| DBusAddWatchFunction add_function, |
| DBusRemoveWatchFunction remove_function, |
| DBusWatchToggledFunction toggled_function, |
| void *data, |
| DBusFreeFunction free_data_function) |
| { |
| dbus_bool_t result; |
| DBusWatchList *watches; |
| |
| _dbus_return_val_if_fail (server != NULL, FALSE); |
| |
| SERVER_LOCK (server); |
| watches = server->watches; |
| server->watches = NULL; |
| if (watches) |
| { |
| SERVER_UNLOCK (server); |
| result = _dbus_watch_list_set_functions (watches, |
| add_function, |
| remove_function, |
| toggled_function, |
| data, |
| free_data_function); |
| SERVER_LOCK (server); |
| } |
| else |
| { |
| _dbus_warn_check_failed ("Re-entrant call to %s\n", _DBUS_FUNCTION_NAME); |
| result = FALSE; |
| } |
| server->watches = watches; |
| SERVER_UNLOCK (server); |
| |
| return result; |
| } |
| |
| /** |
| * Sets the timeout functions for the server. These functions are |
| * responsible for making the application's main loop aware of timeouts. |
| * |
| * This function behaves exactly like dbus_connection_set_timeout_functions(); |
| * see the documentation for that routine. |
| * |
| * @param server the server. |
| * @param add_function function to add a timeout. |
| * @param remove_function function to remove a timeout. |
| * @param toggled_function function to notify when the timeout is enabled/disabled |
| * @param data data to pass to add_function and remove_function. |
| * @param free_data_function function to be called to free the data. |
| * @returns #FALSE on failure (no memory) |
| */ |
| dbus_bool_t |
| dbus_server_set_timeout_functions (DBusServer *server, |
| DBusAddTimeoutFunction add_function, |
| DBusRemoveTimeoutFunction remove_function, |
| DBusTimeoutToggledFunction toggled_function, |
| void *data, |
| DBusFreeFunction free_data_function) |
| { |
| dbus_bool_t result; |
| DBusTimeoutList *timeouts; |
| |
| _dbus_return_val_if_fail (server != NULL, FALSE); |
| |
| SERVER_LOCK (server); |
| timeouts = server->timeouts; |
| server->timeouts = NULL; |
| if (timeouts) |
| { |
| SERVER_UNLOCK (server); |
| result = _dbus_timeout_list_set_functions (timeouts, |
| add_function, |
| remove_function, |
| toggled_function, |
| data, |
| free_data_function); |
| SERVER_LOCK (server); |
| } |
| else |
| { |
| _dbus_warn_check_failed ("Re-entrant call to %s\n", _DBUS_FUNCTION_NAME); |
| result = FALSE; |
| } |
| server->timeouts = timeouts; |
| SERVER_UNLOCK (server); |
| |
| return result; |
| } |
| |
| /** |
| * Sets the authentication mechanisms that this server offers to |
| * clients, as a #NULL-terminated array of mechanism names. This |
| * function only affects connections created <em>after</em> it is |
| * called. Pass #NULL instead of an array to use all available |
| * mechanisms (this is the default behavior). |
| * |
| * The D-Bus specification describes some of the supported mechanisms. |
| * |
| * @param server the server |
| * @param mechanisms #NULL-terminated array of mechanisms |
| * @returns #FALSE if no memory |
| */ |
| dbus_bool_t |
| dbus_server_set_auth_mechanisms (DBusServer *server, |
| const char **mechanisms) |
| { |
| char **copy; |
| |
| _dbus_return_val_if_fail (server != NULL, FALSE); |
| |
| SERVER_LOCK (server); |
| |
| if (mechanisms != NULL) |
| { |
| copy = _dbus_dup_string_array (mechanisms); |
| if (copy == NULL) |
| return FALSE; |
| } |
| else |
| copy = NULL; |
| |
| dbus_free_string_array (server->auth_mechanisms); |
| server->auth_mechanisms = copy; |
| |
| SERVER_UNLOCK (server); |
| |
| return TRUE; |
| } |
| |
| |
| static DBusDataSlotAllocator slot_allocator; |
| _DBUS_DEFINE_GLOBAL_LOCK (server_slots); |
| |
| /** |
| * Allocates an integer ID to be used for storing application-specific |
| * data on any DBusServer. The allocated ID may then be used |
| * with dbus_server_set_data() and dbus_server_get_data(). |
| * The slot must be initialized with -1. If a nonnegative |
| * slot is passed in, the refcount is incremented on that |
| * slot, rather than creating a new slot. |
| * |
| * The allocated slot is global, i.e. all DBusServer objects will have |
| * a slot with the given integer ID reserved. |
| * |
| * @param slot_p address of global variable storing the slot ID |
| * @returns #FALSE on no memory |
| */ |
| dbus_bool_t |
| dbus_server_allocate_data_slot (dbus_int32_t *slot_p) |
| { |
| return _dbus_data_slot_allocator_alloc (&slot_allocator, |
| (DBusMutex **)&_DBUS_LOCK_NAME (server_slots), |
| slot_p); |
| } |
| |
| /** |
| * Deallocates a global ID for server data slots. |
| * dbus_server_get_data() and dbus_server_set_data() |
| * may no longer be used with this slot. |
| * Existing data stored on existing DBusServer objects |
| * will be freed when the server is finalized, |
| * but may not be retrieved (and may only be replaced |
| * if someone else reallocates the slot). |
| * |
| * @param slot_p address of the slot to deallocate |
| */ |
| void |
| dbus_server_free_data_slot (dbus_int32_t *slot_p) |
| { |
| _dbus_return_if_fail (*slot_p >= 0); |
| |
| _dbus_data_slot_allocator_free (&slot_allocator, slot_p); |
| } |
| |
| /** |
| * Stores a pointer on a DBusServer, along |
| * with an optional function to be used for freeing |
| * the data when the data is set again, or when |
| * the server is finalized. The slot number |
| * must have been allocated with dbus_server_allocate_data_slot(). |
| * |
| * @param server the server |
| * @param slot the slot number |
| * @param data the data to store |
| * @param free_data_func finalizer function for the data |
| * @returns #TRUE if there was enough memory to store the data |
| */ |
| dbus_bool_t |
| dbus_server_set_data (DBusServer *server, |
| int slot, |
| void *data, |
| DBusFreeFunction free_data_func) |
| { |
| DBusFreeFunction old_free_func; |
| void *old_data; |
| dbus_bool_t retval; |
| |
| _dbus_return_val_if_fail (server != NULL, FALSE); |
| |
| SERVER_LOCK (server); |
| |
| retval = _dbus_data_slot_list_set (&slot_allocator, |
| &server->slot_list, |
| slot, data, free_data_func, |
| &old_free_func, &old_data); |
| |
| |
| SERVER_UNLOCK (server); |
| |
| if (retval) |
| { |
| /* Do the actual free outside the server lock */ |
| if (old_free_func) |
| (* old_free_func) (old_data); |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * Retrieves data previously set with dbus_server_set_data(). |
| * The slot must still be allocated (must not have been freed). |
| * |
| * @param server the server |
| * @param slot the slot to get data from |
| * @returns the data, or #NULL if not found |
| */ |
| void* |
| dbus_server_get_data (DBusServer *server, |
| int slot) |
| { |
| void *res; |
| |
| _dbus_return_val_if_fail (server != NULL, NULL); |
| |
| SERVER_LOCK (server); |
| |
| res = _dbus_data_slot_list_get (&slot_allocator, |
| &server->slot_list, |
| slot); |
| |
| SERVER_UNLOCK (server); |
| |
| return res; |
| } |
| |
| /** @} */ |
| |
| #ifdef DBUS_BUILD_TESTS |
| #include "dbus-test.h" |
| #include <string.h> |
| |
| dbus_bool_t |
| _dbus_server_test (void) |
| { |
| const char *valid_addresses[] = { |
| "tcp:port=1234", |
| "tcp:host=localhost,port=1234", |
| "tcp:host=localhost,port=1234;tcp:port=5678", |
| #ifdef DBUS_UNIX |
| "unix:path=./boogie", |
| "tcp:port=1234;unix:path=./boogie", |
| #endif |
| }; |
| |
| DBusServer *server; |
| int i; |
| |
| for (i = 0; i < _DBUS_N_ELEMENTS (valid_addresses); i++) |
| { |
| DBusError error = DBUS_ERROR_INIT; |
| char *address; |
| char *id; |
| |
| server = dbus_server_listen (valid_addresses[i], &error); |
| if (server == NULL) |
| { |
| _dbus_warn ("server listen error: %s: %s\n", error.name, error.message); |
| dbus_error_free (&error); |
| _dbus_assert_not_reached ("Failed to listen for valid address."); |
| } |
| |
| id = dbus_server_get_id (server); |
| _dbus_assert (id != NULL); |
| address = dbus_server_get_address (server); |
| _dbus_assert (address != NULL); |
| |
| if (strstr (address, id) == NULL) |
| { |
| _dbus_warn ("server id '%s' is not in the server address '%s'\n", |
| id, address); |
| _dbus_assert_not_reached ("bad server id or address"); |
| } |
| |
| dbus_free (id); |
| dbus_free (address); |
| |
| dbus_server_disconnect (server); |
| dbus_server_unref (server); |
| } |
| |
| return TRUE; |
| } |
| |
| #endif /* DBUS_BUILD_TESTS */ |