/* -*- 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 #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; server->published_address = FALSE; 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_IS_CLEAR (&tmp_error); handled_once = TRUE; goto out; } else if (result == DBUS_SERVER_LISTEN_ADDRESS_ALREADY_USED) { _dbus_assert (server == NULL); dbus_set_error (error, DBUS_ERROR_ADDRESS_IN_USE, "Address '%s' already used", dbus_address_entry_get_method (entries[0])); handled_once = TRUE; goto out; } else if (result == DBUS_SERVER_LISTEN_BAD_ADDRESS) { _dbus_assert (server == NULL); _DBUS_ASSERT_ERROR_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_IS_CLEAR (&tmp_error); /* keep trying addresses */ } else if (result == DBUS_SERVER_LISTEN_DID_NOT_CONNECT) { _dbus_assert (server == NULL); _DBUS_ASSERT_ERROR_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_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 after 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 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 */