X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=bus%2Fservices.c;h=0a049257118f7c24ce3af0797515a7bdc934e08f;hb=dbecdeabb20e0ce11121819c63373f0afba57c58;hp=f8124b0537d14668659e2d08ca2c672acdb2632e;hpb=96a9f80300b7794475a5451a60a07555ea3526be;p=platform%2Fupstream%2Fdbus.git diff --git a/bus/services.c b/bus/services.c index f8124b0..0a04925 100644 --- a/bus/services.c +++ b/bus/services.c @@ -1,9 +1,10 @@ -/* -*- mode: C; c-file-style: "gnu" -*- */ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* services.c Service management * * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2003 CodeFactory AB * - * Licensed under the Academic Free License version 1.2 + * 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 @@ -17,113 +18,1222 @@ * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ -#include "services.h" -#include "connection.h" + +#include #include #include #include +#include + +#include "driver.h" +#include "services.h" +#include "connection.h" +#include "utils.h" +#include "activation.h" +#include "policy.h" +#include "bus.h" +#include "selinux.h" struct BusService { + int refcount; + + BusRegistry *registry; char *name; DBusList *owners; }; -static DBusHashTable *service_hash = NULL; -static DBusMemPool *service_pool = NULL; +struct BusOwner +{ + int refcount; + + BusService *service; + DBusConnection *conn; + + unsigned int allow_replacement : 1; + unsigned int do_not_queue : 1; +}; + +struct BusRegistry +{ + int refcount; + + BusContext *context; + + DBusHashTable *service_hash; + DBusMemPool *service_pool; + DBusMemPool *owner_pool; + + DBusHashTable *service_sid_table; +}; + +BusRegistry* +bus_registry_new (BusContext *context) +{ + BusRegistry *registry; + + registry = dbus_new0 (BusRegistry, 1); + if (registry == NULL) + return NULL; + + registry->refcount = 1; + registry->context = context; + + registry->service_hash = _dbus_hash_table_new (DBUS_HASH_STRING, + NULL, NULL); + if (registry->service_hash == NULL) + goto failed; + + registry->service_pool = _dbus_mem_pool_new (sizeof (BusService), + TRUE); + + if (registry->service_pool == NULL) + goto failed; + + registry->owner_pool = _dbus_mem_pool_new (sizeof (BusOwner), + TRUE); + + if (registry->owner_pool == NULL) + goto failed; + + registry->service_sid_table = NULL; + + return registry; + + failed: + bus_registry_unref (registry); + return NULL; +} + +BusRegistry * +bus_registry_ref (BusRegistry *registry) +{ + _dbus_assert (registry->refcount > 0); + registry->refcount += 1; + + return registry; +} + +void +bus_registry_unref (BusRegistry *registry) +{ + _dbus_assert (registry->refcount > 0); + registry->refcount -= 1; + + if (registry->refcount == 0) + { + if (registry->service_hash) + _dbus_hash_table_unref (registry->service_hash); + if (registry->service_pool) + _dbus_mem_pool_free (registry->service_pool); + if (registry->owner_pool) + _dbus_mem_pool_free (registry->owner_pool); + if (registry->service_sid_table) + _dbus_hash_table_unref (registry->service_sid_table); + + dbus_free (registry); + } +} BusService* -bus_service_lookup (const DBusString *service_name, - dbus_bool_t create_if_not_found) +bus_registry_lookup (BusRegistry *registry, + const DBusString *service_name) { - const char *c_name; BusService *service; + + service = _dbus_hash_table_lookup_string (registry->service_hash, + _dbus_string_get_const_data (service_name)); + + return service; +} + +static DBusList * +_bus_service_find_owner_link (BusService *service, + DBusConnection *connection) +{ + DBusList *link; - if (service_hash == NULL) + link = _dbus_list_get_first_link (&service->owners); + + while (link != NULL) + { + BusOwner *bus_owner; + + bus_owner = (BusOwner *) link->data; + if (bus_owner->conn == connection) + break; + + link = _dbus_list_get_next_link (&service->owners, link); + } + + return link; +} + +static void +bus_owner_set_flags (BusOwner *owner, + dbus_uint32_t flags) +{ + owner->allow_replacement = + (flags & DBUS_NAME_FLAG_ALLOW_REPLACEMENT) != FALSE; + + owner->do_not_queue = + (flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) != FALSE; +} + +static BusOwner * +bus_owner_new (BusService *service, + DBusConnection *conn, + dbus_uint32_t flags) +{ + BusOwner *result; + + result = _dbus_mem_pool_alloc (service->registry->owner_pool); + if (result != NULL) { - service_hash = _dbus_hash_table_new (DBUS_HASH_STRING, - NULL, NULL); - service_pool = _dbus_mem_pool_new (sizeof (BusService), - TRUE); + result->refcount = 1; + /* don't ref the connection because we don't want + to block the connection from going away. + transactions take care of reffing the connection + but we need to use refcounting on the owner + so that the owner does not get freed before + we can deref the connection in the transaction + */ + result->conn = conn; + result->service = service; - if (service_hash == NULL || service_pool == NULL) + if (!bus_connection_add_owned_service (conn, service)) { - if (service_hash) - { - _dbus_hash_table_unref (service_hash); - service_hash = NULL; - } - if (service_pool) - { - _dbus_mem_pool_free (service_pool); - service_pool = NULL; - } + _dbus_mem_pool_dealloc (service->registry->owner_pool, result); return NULL; } + + bus_owner_set_flags (result, flags); + } + return result; +} + +static BusOwner * +bus_owner_ref (BusOwner *owner) +{ + _dbus_assert (owner->refcount > 0); + owner->refcount += 1; + + return owner; +} + +static void +bus_owner_unref (BusOwner *owner) +{ + _dbus_assert (owner->refcount > 0); + owner->refcount -= 1; + + if (owner->refcount == 0) + { + bus_connection_remove_owned_service (owner->conn, owner->service); + _dbus_mem_pool_dealloc (owner->service->registry->owner_pool, owner); } +} + +BusService* +bus_registry_ensure (BusRegistry *registry, + const DBusString *service_name, + DBusConnection *owner_connection_if_created, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error) +{ + BusService *service; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); - _dbus_string_get_const_data (service_name, &c_name); + _dbus_assert (owner_connection_if_created != NULL); + _dbus_assert (transaction != NULL); - service = _dbus_hash_table_lookup_string (service_hash, - c_name); + service = _dbus_hash_table_lookup_string (registry->service_hash, + _dbus_string_get_const_data (service_name)); if (service != NULL) return service; - - if (!create_if_not_found) - return NULL; - service = _dbus_mem_pool_alloc (service_pool); + service = _dbus_mem_pool_alloc (registry->service_pool); if (service == NULL) - return NULL; + { + BUS_SET_OOM (error); + return NULL; + } + + service->registry = registry; + service->refcount = 1; + + _dbus_verbose ("copying string %p '%s' to service->name\n", + service_name, _dbus_string_get_const_data (service_name)); + if (!_dbus_string_copy_data (service_name, &service->name)) + { + _dbus_mem_pool_dealloc (registry->service_pool, service); + BUS_SET_OOM (error); + return NULL; + } + _dbus_verbose ("copied string %p '%s' to '%s'\n", + service_name, _dbus_string_get_const_data (service_name), + service->name); - service->name = _dbus_strdup (c_name); - if (service->name == NULL) + if (!bus_driver_send_service_owner_changed (service->name, + NULL, + bus_connection_get_name (owner_connection_if_created), + transaction, error)) { - _dbus_mem_pool_dealloc (service_pool, service); + bus_service_unref (service); return NULL; } - if (!_dbus_hash_table_insert_string (service_hash, + if (!bus_activation_service_created (bus_context_get_activation (registry->context), + service->name, transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + if (!bus_service_add_owner (service, owner_connection_if_created, flags, + transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + if (!_dbus_hash_table_insert_string (registry->service_hash, service->name, service)) { - dbus_free (service->name); - _dbus_mem_pool_dealloc (service_pool, service); + /* The add_owner gets reverted on transaction cancel */ + BUS_SET_OOM (error); return NULL; } - + return service; } +void +bus_registry_foreach (BusRegistry *registry, + BusServiceForeachFunction function, + void *data) +{ + DBusHashIter iter; + + _dbus_hash_iter_init (registry->service_hash, &iter); + while (_dbus_hash_iter_next (&iter)) + { + BusService *service = _dbus_hash_iter_get_value (&iter); + + (* function) (service, data); + } +} + +dbus_bool_t +bus_registry_list_services (BusRegistry *registry, + char ***listp, + int *array_len) +{ + int i, j, len; + char **retval; + DBusHashIter iter; + + len = _dbus_hash_table_get_n_entries (registry->service_hash); + retval = dbus_new (char *, len + 1); + + if (retval == NULL) + return FALSE; + + _dbus_hash_iter_init (registry->service_hash, &iter); + i = 0; + while (_dbus_hash_iter_next (&iter)) + { + BusService *service = _dbus_hash_iter_get_value (&iter); + + retval[i] = _dbus_strdup (service->name); + if (retval[i] == NULL) + goto error; + + i++; + } + + retval[i] = NULL; + + if (array_len) + *array_len = len; + + *listp = retval; + return TRUE; + + error: + for (j = 0; j < i; j++) + dbus_free (retval[i]); + dbus_free (retval); + + return FALSE; +} + +dbus_bool_t +bus_registry_acquire_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t flags, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error) +{ + dbus_bool_t retval; + DBusConnection *old_owner_conn; + DBusConnection *current_owner_conn; + BusClientPolicy *policy; + BusService *service; + BusActivation *activation; + BusSELinuxID *sid; + BusOwner *primary_owner; + + retval = FALSE; + + if (!_dbus_validate_bus_name (service_name, 0, + _dbus_string_get_length (service_name))) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Requested bus name \"%s\" is not valid", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to acquire invalid service name\n"); + + goto out; + } + + if (_dbus_string_get_byte (service_name, 0) == ':') + { + /* Not allowed; only base services can start with ':' */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot acquire a service starting with ':' such as \"%s\"", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to acquire invalid base service name \"%s\"", + _dbus_string_get_const_data (service_name)); + + goto out; + } + + if (_dbus_string_equal_c_str (service_name, DBUS_SERVICE_DBUS)) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Connection \"%s\" is not allowed to own the service \"%s\"because " + "it is reserved for D-Bus' use only", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + DBUS_SERVICE_DBUS); + goto out; + } + + policy = bus_connection_get_policy (connection); + _dbus_assert (policy != NULL); + + /* Note that if sid is #NULL then the bus's own context gets used + * in bus_connection_selinux_allows_acquire_service() + */ + sid = bus_selinux_id_table_lookup (registry->service_sid_table, + service_name); + + if (!bus_selinux_allows_acquire_service (connection, sid, + _dbus_string_get_const_data (service_name), error)) + { + + if (dbus_error_is_set (error) && + dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + { + goto out; + } + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to own the service \"%s\" due " + "to SELinux policy", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + _dbus_string_get_const_data (service_name)); + goto out; + } + + if (!bus_client_policy_check_can_own (policy, connection, + service_name)) + { + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to own the service \"%s\" due " + "to security policies in the configuration file", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + _dbus_string_get_const_data (service_name)); + goto out; + } + + if (bus_connection_get_n_services_owned (connection) >= + bus_context_get_max_services_per_connection (registry->context)) + { + dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, + "Connection \"%s\" is not allowed to own more services " + "(increase limits in configuration file if required)", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)"); + goto out; + } + + service = bus_registry_lookup (registry, service_name); + + if (service != NULL) + { + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner != NULL) + old_owner_conn = primary_owner->conn; + else + old_owner_conn = NULL; + } + else + old_owner_conn = NULL; + + if (service == NULL) + { + service = bus_registry_ensure (registry, + service_name, connection, flags, + transaction, error); + if (service == NULL) + goto out; + } + + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner == NULL) + goto out; + + current_owner_conn = primary_owner->conn; + + if (old_owner_conn == NULL) + { + _dbus_assert (current_owner_conn == connection); + + *result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + } + else if (old_owner_conn == connection) + { + bus_owner_set_flags (primary_owner, flags); + *result = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER; + } + else if (((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + !(bus_service_get_allow_replacement (service))) || + ((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + !(flags & DBUS_NAME_FLAG_REPLACE_EXISTING))) + { + DBusList *link; + BusOwner *temp_owner; + /* Since we can't be queued if we are already in the queue + remove us */ + + link = _bus_service_find_owner_link (service, connection); + if (link != NULL) + { + _dbus_list_unlink (&service->owners, link); + temp_owner = (BusOwner *)link->data; + bus_owner_unref (temp_owner); + _dbus_list_free_link (link); + } + + *result = DBUS_REQUEST_NAME_REPLY_EXISTS; + } + else if (!(flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) && + (!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) || + !(bus_service_get_allow_replacement (service)))) + { + /* Queue the connection */ + if (!bus_service_add_owner (service, connection, + flags, + transaction, error)) + goto out; + + *result = DBUS_REQUEST_NAME_REPLY_IN_QUEUE; + } + else + { + /* Replace the current owner */ + + /* We enqueue the new owner and remove the first one because + * that will cause NameAcquired and NameLost messages to + * be sent. + */ + + if (!bus_service_add_owner (service, connection, + flags, + transaction, error)) + goto out; + + if (primary_owner->do_not_queue) + { + if (!bus_service_remove_owner (service, old_owner_conn, + transaction, error)) + goto out; + } + else + { + if (!bus_service_swap_owner (service, old_owner_conn, + transaction, error)) + goto out; + } + + + _dbus_assert (connection == bus_service_get_primary_owner (service)->conn); + *result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + } + + activation = bus_context_get_activation (registry->context); + retval = bus_activation_send_pending_auto_activation_messages (activation, + service, + transaction, + error); + + out: + return retval; +} + +dbus_bool_t +bus_registry_release_service (BusRegistry *registry, + DBusConnection *connection, + const DBusString *service_name, + dbus_uint32_t *result, + BusTransaction *transaction, + DBusError *error) +{ + dbus_bool_t retval; + BusService *service; + + retval = FALSE; + + if (!_dbus_validate_bus_name (service_name, 0, + _dbus_string_get_length (service_name))) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Given bus name \"%s\" is not valid", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to release invalid service name\n"); + + goto out; + } + + if (_dbus_string_get_byte (service_name, 0) == ':') + { + /* Not allowed; the base service name cannot be created or released */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot release a service starting with ':' such as \"%s\"", + _dbus_string_get_const_data (service_name)); + + _dbus_verbose ("Attempt to release invalid base service name \"%s\"", + _dbus_string_get_const_data (service_name)); + + goto out; + } + + if (_dbus_string_equal_c_str (service_name, DBUS_SERVICE_DBUS)) + { + /* Not allowed; the base service name cannot be created or released */ + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Cannot release the %s service because it is owned by the bus", + DBUS_SERVICE_DBUS); + + _dbus_verbose ("Attempt to release service name \"%s\"", + DBUS_SERVICE_DBUS); + + goto out; + } + + service = bus_registry_lookup (registry, service_name); + + if (service == NULL) + { + *result = DBUS_RELEASE_NAME_REPLY_NON_EXISTENT; + } + else if (!bus_service_has_owner (service, connection)) + { + *result = DBUS_RELEASE_NAME_REPLY_NOT_OWNER; + } + else + { + if (!bus_service_remove_owner (service, connection, + transaction, error)) + goto out; + + _dbus_assert (!bus_service_has_owner (service, connection)); + *result = DBUS_RELEASE_NAME_REPLY_RELEASED; + } + + retval = TRUE; + + out: + return retval; +} + +dbus_bool_t +bus_registry_set_service_context_table (BusRegistry *registry, + DBusHashTable *table) +{ + DBusHashTable *new_table; + DBusHashIter iter; + + new_table = bus_selinux_id_table_new (); + if (!new_table) + return FALSE; + + _dbus_hash_iter_init (table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + const char *service = _dbus_hash_iter_get_string_key (&iter); + const char *context = _dbus_hash_iter_get_value (&iter); + + if (!bus_selinux_id_table_insert (new_table, + service, + context)) + return FALSE; + } + + if (registry->service_sid_table) + _dbus_hash_table_unref (registry->service_sid_table); + registry->service_sid_table = new_table; + return TRUE; +} + +static void +bus_service_unlink_owner (BusService *service, + BusOwner *owner) +{ + _dbus_list_remove_last (&service->owners, owner); + bus_owner_unref (owner); +} + +static void +bus_service_unlink (BusService *service) +{ + _dbus_assert (service->owners == NULL); + + /* the service may not be in the hash, if + * the failure causing transaction cancel + * was in the right place, but that's OK + */ + _dbus_hash_table_remove_string (service->registry->service_hash, + service->name); + + bus_service_unref (service); +} + +static void +bus_service_relink (BusService *service, + DBusPreallocatedHash *preallocated) +{ + _dbus_assert (service->owners == NULL); + _dbus_assert (preallocated != NULL); + + _dbus_hash_table_insert_string_preallocated (service->registry->service_hash, + preallocated, + service->name, + service); + + bus_service_ref (service); +} + +/** + * Data used to represent an ownership cancellation in + * a bus transaction. + */ +typedef struct +{ + BusOwner *owner; /**< the owner */ + BusService *service; /**< service to cancel ownership of */ +} OwnershipCancelData; + +static void +cancel_ownership (void *data) +{ + OwnershipCancelData *d = data; + + /* We don't need to send messages notifying of these + * changes, since we're reverting something that was + * cancelled (effectively never really happened) + */ + bus_service_unlink_owner (d->service, d->owner); + + if (d->service->owners == NULL) + bus_service_unlink (d->service); +} + +static void +free_ownership_cancel_data (void *data) +{ + OwnershipCancelData *d = data; + + dbus_connection_unref (d->owner->conn); + bus_owner_unref (d->owner); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_cancel_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + BusOwner *owner) +{ + OwnershipCancelData *d; + + d = dbus_new (OwnershipCancelData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->owner = owner; + + if (!bus_transaction_add_cancel_hook (transaction, cancel_ownership, d, + free_ownership_cancel_data)) + { + dbus_free (d); + return FALSE; + } + + bus_service_ref (d->service); + bus_owner_ref (owner); + dbus_connection_ref (d->owner->conn); + + return TRUE; +} + +/* this function is self-cancelling if you cancel the transaction */ dbus_bool_t bus_service_add_owner (BusService *service, - DBusConnection *owner) + DBusConnection *connection, + dbus_uint32_t flags, + BusTransaction *transaction, + DBusError *error) +{ + BusOwner *bus_owner; + DBusList *bus_owner_link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Send service acquired message first, OOM will result + * in cancelling the transaction + */ + if (service->owners == NULL) + { + if (!bus_driver_send_service_acquired (connection, service->name, transaction, error)) + return FALSE; + } + + bus_owner_link = _bus_service_find_owner_link (service, connection); + + if (bus_owner_link == NULL) + { + bus_owner = bus_owner_new (service, connection, flags); + if (bus_owner == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + bus_owner_set_flags (bus_owner, flags); + if (!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) || service->owners == NULL) + { + if (!_dbus_list_append (&service->owners, + bus_owner)) + { + bus_owner_unref (bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + } + else + { + if (!_dbus_list_insert_after (&service->owners, + _dbus_list_get_first_link (&service->owners), + bus_owner)) + { + bus_owner_unref (bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + } + } + else + { + /* Update the link since we are already in the queue + * No need for operations that can produce OOM + */ + + bus_owner = (BusOwner *) bus_owner_link->data; + if (flags & DBUS_NAME_FLAG_REPLACE_EXISTING) + { + DBusList *link; + _dbus_list_unlink (&service->owners, bus_owner_link); + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + + _dbus_list_insert_after_link (&service->owners, link, bus_owner_link); + } + + bus_owner_set_flags (bus_owner, flags); + return TRUE; + } + + if (!add_cancel_ownership_to_transaction (transaction, + service, + bus_owner)) + { + bus_service_unlink_owner (service, bus_owner); + BUS_SET_OOM (error); + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + BusOwner *owner; + BusService *service; + BusOwner *before_owner; /* restore to position before this connection in owners list */ + DBusList *owner_link; + DBusList *service_link; + DBusPreallocatedHash *hash_entry; +} OwnershipRestoreData; + +static void +restore_ownership (void *data) +{ + OwnershipRestoreData *d = data; + DBusList *link; + + _dbus_assert (d->service_link != NULL); + _dbus_assert (d->owner_link != NULL); + + if (d->service->owners == NULL) + { + _dbus_assert (d->hash_entry != NULL); + bus_service_relink (d->service, d->hash_entry); + } + else + { + _dbus_assert (d->hash_entry == NULL); + } + + /* We don't need to send messages notifying of these + * changes, since we're reverting something that was + * cancelled (effectively never really happened) + */ + link = _dbus_list_get_first_link (&d->service->owners); + while (link != NULL) + { + if (link->data == d->before_owner) + break; + + link = _dbus_list_get_next_link (&d->service->owners, link); + } + + _dbus_list_insert_before_link (&d->service->owners, link, d->owner_link); + + /* Note that removing then restoring this changes the order in which + * ServiceDeleted messages are sent on destruction of the + * connection. This should be OK as the only guarantee there is + * that the base service is destroyed last, and we never even + * tentatively remove the base service. + */ + bus_connection_add_owned_service_link (d->owner->conn, d->service_link); + + d->hash_entry = NULL; + d->service_link = NULL; + d->owner_link = NULL; +} + +static void +free_ownership_restore_data (void *data) +{ + OwnershipRestoreData *d = data; + + if (d->service_link) + _dbus_list_free_link (d->service_link); + if (d->owner_link) + _dbus_list_free_link (d->owner_link); + if (d->hash_entry) + _dbus_hash_table_free_preallocated_entry (d->service->registry->service_hash, + d->hash_entry); + + dbus_connection_unref (d->owner->conn); + bus_owner_unref (d->owner); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_restore_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + BusOwner *owner) { - if (!_dbus_list_append (&service->owners, - owner)) + OwnershipRestoreData *d; + DBusList *link; + + d = dbus_new (OwnershipRestoreData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->owner = owner; + d->service_link = _dbus_list_alloc_link (service); + d->owner_link = _dbus_list_alloc_link (owner); + d->hash_entry = _dbus_hash_table_preallocate_entry (service->registry->service_hash); + + bus_service_ref (d->service); + bus_owner_ref (d->owner); + dbus_connection_ref (d->owner->conn); + + d->before_owner = NULL; + link = _dbus_list_get_first_link (&service->owners); + while (link != NULL) + { + if (link->data == owner) + { + link = _dbus_list_get_next_link (&service->owners, link); + + if (link) + d->before_owner = link->data; + + break; + } + + link = _dbus_list_get_next_link (&service->owners, link); + } + + if (d->service_link == NULL || + d->owner_link == NULL || + d->hash_entry == NULL || + !bus_transaction_add_cancel_hook (transaction, restore_ownership, d, + free_ownership_restore_data)) + { + free_ownership_restore_data (d); + return FALSE; + } + + return TRUE; +} + +dbus_bool_t +bus_service_swap_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error) +{ + DBusList *swap_link; + BusOwner *primary_owner; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* We send out notifications before we do any work we + * might have to undo if the notification-sending failed + */ + + /* Send service lost message */ + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner == NULL || primary_owner->conn != connection) + _dbus_assert_not_reached ("Tried to swap a non primary owner"); + + + if (!bus_driver_send_service_lost (connection, service->name, + transaction, error)) return FALSE; - if (!bus_connection_add_owned_service (owner, service)) + if (service->owners == NULL) + { + _dbus_assert_not_reached ("Tried to swap owner of a service that has no owners"); + } + else if (_dbus_list_length_is_one (&service->owners)) + { + _dbus_assert_not_reached ("Tried to swap owner of a service that has no other owners in the queue"); + } + else + { + DBusList *link; + BusOwner *new_owner; + DBusConnection *new_owner_conn; + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + link = _dbus_list_get_next_link (&service->owners, link); + _dbus_assert (link != NULL); + + new_owner = (BusOwner *)link->data; + new_owner_conn = new_owner->conn; + + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + bus_connection_get_name (new_owner_conn), + transaction, error)) + return FALSE; + + /* This will be our new owner */ + if (!bus_driver_send_service_acquired (new_owner_conn, + service->name, + transaction, + error)) + return FALSE; + } + + if (!add_restore_ownership_to_transaction (transaction, service, primary_owner)) { - _dbus_list_remove_last (&service->owners, owner); + BUS_SET_OOM (error); return FALSE; } + /* unlink the primary and make it the second link */ + swap_link = _dbus_list_get_first_link (&service->owners); + _dbus_list_unlink (&service->owners, swap_link); + + _dbus_list_insert_after_link (&service->owners, + _dbus_list_get_first_link (&service->owners), + swap_link); + return TRUE; } -void +/* this function is self-cancelling if you cancel the transaction */ +dbus_bool_t bus_service_remove_owner (BusService *service, - DBusConnection *owner) + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error) { - _dbus_list_remove_last (&service->owners, owner); - bus_connection_remove_owned_service (owner, service); + BusOwner *primary_owner; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* We send out notifications before we do any work we + * might have to undo if the notification-sending failed + */ + + /* Send service lost message */ + primary_owner = bus_service_get_primary_owner (service); + if (primary_owner != NULL && primary_owner->conn == connection) + { + if (!bus_driver_send_service_lost (connection, service->name, + transaction, error)) + return FALSE; + } + else + { + /* if we are not the primary owner then just remove us from the queue */ + DBusList *link; + BusOwner *temp_owner; + + link = _bus_service_find_owner_link (service, connection); + _dbus_list_unlink (&service->owners, link); + temp_owner = (BusOwner *)link->data; + bus_owner_unref (temp_owner); + _dbus_list_free_link (link); + + return TRUE; + } + + if (service->owners == NULL) + { + _dbus_assert_not_reached ("Tried to remove owner of a service that has no owners"); + } + else if (_dbus_list_length_is_one (&service->owners)) + { + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + NULL, + transaction, error)) + return FALSE; + } + else + { + DBusList *link; + BusOwner *new_owner; + DBusConnection *new_owner_conn; + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); + link = _dbus_list_get_next_link (&service->owners, link); + _dbus_assert (link != NULL); + + new_owner = (BusOwner *)link->data; + new_owner_conn = new_owner->conn; + + if (!bus_driver_send_service_owner_changed (service->name, + bus_connection_get_name (connection), + bus_connection_get_name (new_owner_conn), + transaction, error)) + return FALSE; + + /* This will be our new owner */ + if (!bus_driver_send_service_acquired (new_owner_conn, + service->name, + transaction, + error)) + return FALSE; + } + + if (!add_restore_ownership_to_transaction (transaction, service, primary_owner)) + { + BUS_SET_OOM (error); + return FALSE; + } + + bus_service_unlink_owner (service, primary_owner); + + if (service->owners == NULL) + bus_service_unlink (service); + + return TRUE; +} + +BusService * +bus_service_ref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount += 1; + + return service; +} + +void +bus_service_unref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount -= 1; + + if (service->refcount == 0) + { + _dbus_assert (service->owners == NULL); + + dbus_free (service->name); + _dbus_mem_pool_dealloc (service->registry->service_pool, service); + } +} + +DBusConnection * +bus_service_get_primary_owners_connection (BusService *service) +{ + BusOwner *owner; + + owner = bus_service_get_primary_owner (service); + + if (owner != NULL) + return owner->conn; + else + return NULL; } -DBusConnection* +BusOwner* bus_service_get_primary_owner (BusService *service) { return _dbus_list_get_first (&service->owners); @@ -135,20 +1245,64 @@ bus_service_get_name (BusService *service) return service->name; } -void -bus_service_foreach (BusServiceForeachFunction function, - void *data) +dbus_bool_t +bus_service_get_allow_replacement (BusService *service) { - DBusHashIter iter; - - if (service_hash == NULL) - return; + BusOwner *owner; + DBusList *link; + + _dbus_assert (service->owners != NULL); + + link = _dbus_list_get_first_link (&service->owners); + owner = (BusOwner *) link->data; + + return owner->allow_replacement; +} + +dbus_bool_t +bus_service_has_owner (BusService *service, + DBusConnection *connection) +{ + DBusList *link; + + link = _bus_service_find_owner_link (service, connection); + + if (link == NULL) + return FALSE; + else + return TRUE; +} + +dbus_bool_t +bus_service_list_queued_owners (BusService *service, + DBusList **return_list, + DBusError *error) +{ + DBusList *link; + + _dbus_assert (*return_list == NULL); + + link = _dbus_list_get_first_link (&service->owners); + _dbus_assert (link != NULL); - _dbus_hash_iter_init (service_hash, &iter); - while (_dbus_hash_iter_next (&iter)) + while (link != NULL) { - BusService *service = _dbus_hash_iter_get_value (&iter); + BusOwner *owner; + const char *uname; - (* function) (service, data); + owner = (BusOwner *) link->data; + uname = bus_connection_get_name (owner->conn); + + if (!_dbus_list_append (return_list, (char *)uname)) + goto oom; + + link = _dbus_list_get_next_link (&service->owners, link); } + + return TRUE; + + oom: + _dbus_list_clear (return_list); + BUS_SET_OOM (error); + return FALSE; }