diff options
author | Simon McVittie <smcv@debian.org> | 2011-01-31 17:37:59 +0000 |
---|---|---|
committer | Simon McVittie <smcv@debian.org> | 2011-01-31 17:37:59 +0000 |
commit | 24fbe571516161d48b499d587f9adb3e683dbf88 (patch) | |
tree | 7d70909156dcf587d91f693b8e1216eb4e0465e1 /bus/services.c | |
parent | 9b72896b3730a9fceb961be28bb95762a7b4e9d6 (diff) | |
download | dbus-24fbe571516161d48b499d587f9adb3e683dbf88.tar.gz |
Imported Upstream version 1.2.24upstream/1.2.24
Diffstat (limited to 'bus/services.c')
-rw-r--r-- | bus/services.c | 1306 |
1 files changed, 1306 insertions, 0 deletions
diff --git a/bus/services.c b/bus/services.c new file mode 100644 index 00000000..b260c633 --- /dev/null +++ b/bus/services.c @@ -0,0 +1,1306 @@ +/* -*- 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 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 <dbus/dbus-hash.h> +#include <dbus/dbus-list.h> +#include <dbus/dbus-mempool.h> +#include <dbus/dbus-marshal-validate.h> + +#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; +}; + +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_registry_lookup (BusRegistry *registry, + const DBusString *service_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; + + 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) + { + 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 (!bus_connection_add_owned_service (conn, service)) + { + _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_assert (owner_connection_if_created != NULL); + _dbus_assert (transaction != NULL); + + service = _dbus_hash_table_lookup_string (registry->service_hash, + _dbus_string_get_const_data (service_name)); + if (service != NULL) + return service; + + service = _dbus_mem_pool_alloc (registry->service_pool); + if (service == 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); + + if (!bus_driver_send_service_owner_changed (service->name, + NULL, + bus_connection_get_name (owner_connection_if_created), + transaction, error)) + { + bus_service_unref (service); + return NULL; + } + + 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)) + { + /* 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 *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) +{ + 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 (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)) + { + 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; +} + +/* this function is self-cancelling if you cancel the transaction */ +dbus_bool_t +bus_service_remove_owner (BusService *service, + DBusConnection *connection, + BusTransaction *transaction, + DBusError *error) +{ + 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; +} + +BusOwner* +bus_service_get_primary_owner (BusService *service) +{ + return _dbus_list_get_first (&service->owners); +} + +const char* +bus_service_get_name (BusService *service) +{ + return service->name; +} + +dbus_bool_t +bus_service_get_allow_replacement (BusService *service) +{ + 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); + + while (link != NULL) + { + BusOwner *owner; + const char *uname; + + 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; +} |