| /* -*- 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 <config.h> |
| #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; |
| 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, 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; |
| |
| if (old_owner_conn == NULL) |
| { |
| _dbus_assert (primary_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; |
| } |