diff options
Diffstat (limited to 'bus')
-rw-r--r-- | bus/activation.c | 106 | ||||
-rw-r--r-- | bus/connection.c | 113 | ||||
-rw-r--r-- | bus/connection.h | 42 | ||||
-rw-r--r-- | bus/dispatch.c | 98 | ||||
-rw-r--r-- | bus/driver.c | 87 | ||||
-rw-r--r-- | bus/services.c | 414 | ||||
-rw-r--r-- | bus/services.h | 71 |
7 files changed, 731 insertions, 200 deletions
diff --git a/bus/activation.c b/bus/activation.c index 13c147ea..64e0d914 100644 --- a/bus/activation.c +++ b/bus/activation.c @@ -63,6 +63,7 @@ struct BusPendingActivationEntry typedef struct { + int refcount; BusActivation *activation; char *service_name; DBusList *entries; @@ -94,13 +95,26 @@ handle_timeout_callback (DBusTimeout *timeout, } static void -bus_pending_activation_free (BusPendingActivation *pending_activation) +bus_pending_activation_ref (BusPendingActivation *pending_activation) +{ + _dbus_assert (pending_activation->refcount > 0); + pending_activation->refcount += 1; +} + +static void +bus_pending_activation_unref (BusPendingActivation *pending_activation) { DBusList *link; if (pending_activation == NULL) /* hash table requires this */ return; + _dbus_assert (pending_activation->refcount > 0); + pending_activation->refcount -= 1; + + if (pending_activation->refcount > 0) + return; + if (pending_activation->timeout_added) { _dbus_loop_remove_timeout (bus_context_get_loop (pending_activation->activation->context), @@ -396,7 +410,7 @@ bus_activation_new (BusContext *context, } activation->pending_activations = _dbus_hash_table_new (DBUS_HASH_STRING, NULL, - (DBusFreeFunction)bus_pending_activation_free); + (DBusFreeFunction)bus_pending_activation_unref); if (activation->pending_activations == NULL) { @@ -466,6 +480,75 @@ child_setup (void *data) } } +typedef struct +{ + BusPendingActivation *pending_activation; + DBusPreallocatedHash *hash_entry; +} RestorePendingData; + +static void +restore_pending (void *data) +{ + RestorePendingData *d = data; + + _dbus_assert (d->pending_activation != NULL); + _dbus_assert (d->hash_entry != NULL); + + _dbus_verbose ("Restoring pending activation for service %s, has timeout = %d\n", + d->pending_activation->service_name, + d->pending_activation->timeout_added); + + _dbus_hash_table_insert_string_preallocated (d->pending_activation->activation->pending_activations, + d->hash_entry, + d->pending_activation->service_name, d->pending_activation); + + bus_pending_activation_ref (d->pending_activation); + + d->hash_entry = NULL; +} + +static void +free_pending_restore_data (void *data) +{ + RestorePendingData *d = data; + + if (d->hash_entry) + _dbus_hash_table_free_preallocated_entry (d->pending_activation->activation->pending_activations, + d->hash_entry); + + bus_pending_activation_unref (d->pending_activation); + + dbus_free (d); +} + +static dbus_bool_t +add_restore_pending_to_transaction (BusTransaction *transaction, + BusPendingActivation *pending_activation) +{ + RestorePendingData *d; + + d = dbus_new (RestorePendingData, 1); + if (d == NULL) + return FALSE; + + d->pending_activation = pending_activation; + d->hash_entry = _dbus_hash_table_preallocate_entry (d->pending_activation->activation->pending_activations); + + bus_pending_activation_ref (d->pending_activation); + + if (d->hash_entry == NULL || + !bus_transaction_add_cancel_hook (transaction, restore_pending, d, + free_pending_restore_data)) + { + free_pending_restore_data (d); + return FALSE; + } + + _dbus_verbose ("Saved pending activation to be restored if the transaction fails\n"); + + return TRUE; +} + dbus_bool_t bus_activation_service_created (BusActivation *activation, const char *service_name, @@ -521,13 +604,19 @@ bus_activation_service_created (BusActivation *activation, link = next; } + + if (!add_restore_pending_to_transaction (transaction, pending_activation)) + { + _dbus_verbose ("Could not add cancel hook to transaction to revert removing pending activation\n"); + BUS_SET_OOM (error); + goto error; + } _dbus_hash_table_remove_string (activation->pending_activations, service_name); return TRUE; error: - _dbus_hash_table_remove_string (activation->pending_activations, service_name); return FALSE; } @@ -785,12 +874,13 @@ bus_activation_activate_service (BusActivation *activation, } pending_activation->activation = activation; + pending_activation->refcount = 1; pending_activation->service_name = _dbus_strdup (service_name); if (!pending_activation->service_name) { BUS_SET_OOM (error); - bus_pending_activation_free (pending_activation); + bus_pending_activation_unref (pending_activation); bus_pending_activation_entry_free (pending_activation_entry); return FALSE; } @@ -803,7 +893,7 @@ bus_activation_activate_service (BusActivation *activation, if (!pending_activation->timeout) { BUS_SET_OOM (error); - bus_pending_activation_free (pending_activation); + bus_pending_activation_unref (pending_activation); bus_pending_activation_entry_free (pending_activation_entry); return FALSE; } @@ -815,7 +905,7 @@ bus_activation_activate_service (BusActivation *activation, NULL)) { BUS_SET_OOM (error); - bus_pending_activation_free (pending_activation); + bus_pending_activation_unref (pending_activation); bus_pending_activation_entry_free (pending_activation_entry); return FALSE; } @@ -825,7 +915,7 @@ bus_activation_activate_service (BusActivation *activation, if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry)) { BUS_SET_OOM (error); - bus_pending_activation_free (pending_activation); + bus_pending_activation_unref (pending_activation); bus_pending_activation_entry_free (pending_activation_entry); return FALSE; } @@ -834,7 +924,7 @@ bus_activation_activate_service (BusActivation *activation, pending_activation->service_name, pending_activation)) { BUS_SET_OOM (error); - bus_pending_activation_free (pending_activation); + bus_pending_activation_unref (pending_activation); return FALSE; } } diff --git a/bus/connection.c b/bus/connection.c index 80a9ae7a..2cfbeb27 100644 --- a/bus/connection.c +++ b/bus/connection.c @@ -139,6 +139,8 @@ bus_connection_disconnected (DBusConnection *connection) if (!bus_service_remove_owner (service, connection, transaction, &error)) { + _DBUS_ASSERT_ERROR_IS_SET (&error); + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) { dbus_error_free (&error); @@ -147,7 +149,11 @@ bus_connection_disconnected (DBusConnection *connection) goto retry; } else - _dbus_assert_not_reached ("Removing service owner failed for non-memory-related reason"); + { + _dbus_verbose ("Failed to remove service owner: %s %s\n", + error.name, error.message); + _dbus_assert_not_reached ("Removing service owner failed for non-memory-related reason"); + } } bus_transaction_execute_and_free (transaction); @@ -746,19 +752,31 @@ bus_connection_send_oom_error (DBusConnection *connection, d->oom_preallocated = NULL; } -dbus_bool_t -bus_connection_add_owned_service (DBusConnection *connection, - BusService *service) +void +bus_connection_add_owned_service_link (DBusConnection *connection, + DBusList *link) { BusConnectionData *d; d = BUS_CONNECTION_DATA (connection); _dbus_assert (d != NULL); - if (!_dbus_list_append (&d->services_owned, - service)) + _dbus_list_append_link (&d->services_owned, link); +} + +dbus_bool_t +bus_connection_add_owned_service (DBusConnection *connection, + BusService *service) +{ + DBusList *link; + + link = _dbus_list_alloc_link (service); + + if (link == NULL) return FALSE; + bus_connection_add_owned_service_link (connection, link); + return TRUE; } @@ -805,6 +823,13 @@ bus_connection_get_name (DBusConnection *connection) return d->name; } +/** + * Transactions + * + * Note that this is fairly fragile; in particular, don't try to use + * one transaction across any main loop iterations. + */ + typedef struct { BusTransaction *transaction; @@ -812,10 +837,18 @@ typedef struct DBusPreallocatedSend *preallocated; } MessageToSend; +typedef struct +{ + BusTransactionCancelFunction cancel_function; + DBusFreeFunction free_data_function; + void *data; +} CancelHook; + struct BusTransaction { DBusList *connections; BusContext *context; + DBusList *cancel_hooks; }; static void @@ -831,6 +864,39 @@ message_to_send_free (DBusConnection *connection, dbus_free (to_send); } +static void +cancel_hook_cancel (void *element, + void *data) +{ + CancelHook *ch = element; + + _dbus_verbose ("Running transaction cancel hook\n"); + + if (ch->cancel_function) + (* ch->cancel_function) (ch->data); +} + +static void +cancel_hook_free (void *element, + void *data) +{ + CancelHook *ch = element; + + if (ch->free_data_function) + (* ch->free_data_function) (ch->data); + + dbus_free (ch); +} + +static void +free_cancel_hooks (BusTransaction *transaction) +{ + _dbus_list_foreach (&transaction->cancel_hooks, + cancel_hook_free, NULL); + + _dbus_list_clear (&transaction->cancel_hooks); +} + BusTransaction* bus_transaction_new (BusContext *context) { @@ -980,6 +1046,11 @@ bus_transaction_cancel_and_free (BusTransaction *transaction) _dbus_assert (transaction->connections == NULL); + _dbus_list_foreach (&transaction->cancel_hooks, + cancel_hook_cancel, NULL); + + free_cancel_hooks (transaction); + dbus_free (transaction); } @@ -1036,6 +1107,8 @@ bus_transaction_execute_and_free (BusTransaction *transaction) _dbus_assert (transaction->connections == NULL); + free_cancel_hooks (transaction); + dbus_free (transaction); } @@ -1090,3 +1163,31 @@ bus_transaction_send_error_reply (BusTransaction *transaction, return TRUE; } + +dbus_bool_t +bus_transaction_add_cancel_hook (BusTransaction *transaction, + BusTransactionCancelFunction cancel_function, + void *data, + DBusFreeFunction free_data_function) +{ + CancelHook *ch; + + ch = dbus_new (CancelHook, 1); + if (ch == NULL) + return FALSE; + + ch->cancel_function = cancel_function; + ch->data = data; + ch->free_data_function = free_data_function; + + /* It's important that the hooks get run in reverse order that they + * were added + */ + if (!_dbus_list_prepend (&transaction->cancel_hooks, ch)) + { + dbus_free (ch); + return FALSE; + } + + return TRUE; +} diff --git a/bus/connection.h b/bus/connection.h index 0d64e987..6108bdfd 100644 --- a/bus/connection.h +++ b/bus/connection.h @@ -25,6 +25,7 @@ #define BUS_CONNECTION_H #include <dbus/dbus.h> +#include <dbus/dbus-list.h> #include "bus.h" typedef dbus_bool_t (* BusConnectionForeachFunction) (DBusConnection *connection, @@ -53,10 +54,13 @@ void bus_connection_send_oom_error (DBusConnection *connection, DBusMessage *in_reply_to); /* called by services.c */ -dbus_bool_t bus_connection_add_owned_service (DBusConnection *connection, - BusService *service); -void bus_connection_remove_owned_service (DBusConnection *connection, - BusService *service); +dbus_bool_t bus_connection_add_owned_service (DBusConnection *connection, + BusService *service); +void bus_connection_remove_owned_service (DBusConnection *connection, + BusService *service); +void bus_connection_add_owned_service_link (DBusConnection *connection, + DBusList *link); + /* called by driver.c */ dbus_bool_t bus_connection_set_name (DBusConnection *connection, @@ -74,18 +78,24 @@ dbus_bool_t bus_connection_get_groups (DBusConnection *connection, BusPolicy* bus_connection_get_policy (DBusConnection *connection); /* transaction API so we can send or not send a block of messages as a whole */ -BusTransaction* bus_transaction_new (BusContext *context); -BusContext* bus_transaction_get_context (BusTransaction *transaction); -BusConnections* bus_transaction_get_connections (BusTransaction *transaction); -dbus_bool_t bus_transaction_send_message (BusTransaction *transaction, - DBusConnection *connection, - DBusMessage *message); -dbus_bool_t bus_transaction_send_error_reply (BusTransaction *transaction, - DBusConnection *connection, - const DBusError *error, - DBusMessage *in_reply_to); -void bus_transaction_cancel_and_free (BusTransaction *transaction); -void bus_transaction_execute_and_free (BusTransaction *transaction); +typedef void (* BusTransactionCancelFunction) (void *data); + +BusTransaction* bus_transaction_new (BusContext *context); +BusContext* bus_transaction_get_context (BusTransaction *transaction); +BusConnections* bus_transaction_get_connections (BusTransaction *transaction); +dbus_bool_t bus_transaction_send_message (BusTransaction *transaction, + DBusConnection *connection, + DBusMessage *message); +dbus_bool_t bus_transaction_send_error_reply (BusTransaction *transaction, + DBusConnection *connection, + const DBusError *error, + DBusMessage *in_reply_to); +void bus_transaction_cancel_and_free (BusTransaction *transaction); +void bus_transaction_execute_and_free (BusTransaction *transaction); +dbus_bool_t bus_transaction_add_cancel_hook (BusTransaction *transaction, + BusTransactionCancelFunction cancel_function, + void *data, + DBusFreeFunction free_data_function); #endif /* BUS_CONNECTION_H */ diff --git a/bus/dispatch.c b/bus/dispatch.c index e867674b..f6ddc76a 100644 --- a/bus/dispatch.c +++ b/bus/dispatch.c @@ -1442,6 +1442,47 @@ check_send_exit_to_service (BusContext *context, return retval; } +static dbus_bool_t +check_got_error (BusContext *context, + DBusConnection *connection, + const char *error_name) +{ + DBusMessage *message; + dbus_bool_t retval; + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not get an expected error\n"); + goto out; + } + + if (!dbus_message_get_is_error (message)) + { + _dbus_warn ("Expected an error, got %s\n", + dbus_message_get_name (message)); + goto out; + } + + if (!dbus_message_name_is (message, error_name)) + { + _dbus_warn ("Expected error %s, got %s instead\n", + error_name, + dbus_message_get_name (message)); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + #define EXISTENT_SERVICE_NAME "org.freedesktop.DBus.TestSuiteEchoService" /* returns TRUE if the correct thing happens, @@ -1551,6 +1592,7 @@ check_existent_service_activation (BusContext *context, else { dbus_bool_t got_service_deleted; + dbus_bool_t got_error; if (!check_base_service_activated (context, connection, message, &base_service)) @@ -1570,9 +1612,22 @@ check_existent_service_activation (BusContext *context, } got_service_deleted = dbus_message_name_is (message, DBUS_MESSAGE_SERVICE_DELETED); - + got_error = dbus_message_get_is_error (message); + dbus_connection_return_message (connection, message); message = NULL; + + if (got_error) + { + if (!check_got_error (context, connection, + DBUS_ERROR_SPAWN_CHILD_EXITED)) + goto out; + + /* A service deleted should be coming along now after this error. + * We can also get the error *after* the service deleted. + */ + got_service_deleted = TRUE; + } if (got_service_deleted) { @@ -1589,34 +1644,19 @@ check_existent_service_activation (BusContext *context, if (csdd.failed) goto out; - /* Now we should get an error about the service exiting */ - block_connection_until_message_from_bus (context, connection); - - /* and process everything again */ - bus_test_run_everything (context); - - message = pop_message_waiting_for_memory (connection); - if (message == NULL) - { - _dbus_warn ("Did not get an error from the service %s exiting\n", - EXISTENT_SERVICE_NAME); - goto out; - } - - if (!dbus_message_get_is_error (message)) - { - _dbus_warn ("Expected an error due to service exiting, got %s\n", - dbus_message_get_name (message)); - goto out; - } - - if (!dbus_message_name_is (message, - DBUS_ERROR_SPAWN_CHILD_EXITED)) + /* Now we should get an error about the service exiting + * if we didn't get it before. + */ + if (!got_error) { - _dbus_warn ("Expected error %s on service exit, got %s instead\n", - DBUS_ERROR_SPAWN_CHILD_EXITED, - dbus_message_get_name (message)); - goto out; + block_connection_until_message_from_bus (context, connection); + + /* and process everything again */ + bus_test_run_everything (context); + + if (!check_got_error (context, connection, + DBUS_ERROR_SPAWN_CHILD_EXITED)) + goto out; } } else @@ -1785,7 +1825,7 @@ bus_dispatch_test (const DBusString *test_data_dir) if (!check_hello_message (context, baz)) _dbus_assert_not_reached ("hello message failed"); -#if 0 +#if 1 check2_try_iterations (context, foo, "existent_service_activation", check_existent_service_activation); #endif diff --git a/bus/driver.c b/bus/driver.c index bb8ac296..33017e9f 100644 --- a/bus/driver.c +++ b/bus/driver.c @@ -438,13 +438,10 @@ bus_driver_handle_acquire_service (DBusConnection *connection, { DBusMessage *reply; DBusString service_name; - BusService *service; char *name; int service_reply; int flags; dbus_bool_t retval; - DBusConnection *old_owner; - DBusConnection *current_owner; BusRegistry *registry; _DBUS_ASSERT_ERROR_IS_CLEAR (error); @@ -461,27 +458,14 @@ bus_driver_handle_acquire_service (DBusConnection *connection, retval = FALSE; reply = NULL; - - if (*name == ':') - { - /* Not allowed; only base services can start with ':' */ - dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, - "Cannot acquire a service starting with ':' such as \"%s\"", - name); - - _dbus_verbose ("Attempt to acquire invalid base service name \"%s\"", name); - - goto out; - } _dbus_string_init_const (&service_name, name); - - service = bus_registry_lookup (registry, &service_name); - if (service != NULL) - old_owner = bus_service_get_primary_owner (service); - else - old_owner = NULL; + if (!bus_registry_acquire_service (registry, connection, + &service_name, flags, + &service_reply, transaction, + error)) + goto out; reply = dbus_message_new_reply (message); if (reply == NULL) @@ -495,67 +479,8 @@ bus_driver_handle_acquire_service (DBusConnection *connection, BUS_SET_OOM (error); goto out; } - - if (service == NULL) - { - service = bus_registry_ensure (registry, - &service_name, connection, transaction, error); - if (service == NULL) - goto out; - } - - current_owner = bus_service_get_primary_owner (service); - - if (old_owner == NULL) - { - _dbus_assert (current_owner == connection); - - bus_service_set_prohibit_replacement (service, - (flags & DBUS_SERVICE_FLAG_PROHIBIT_REPLACEMENT)); - - service_reply = DBUS_SERVICE_REPLY_PRIMARY_OWNER; - } - else if (old_owner == connection) - service_reply = DBUS_SERVICE_REPLY_ALREADY_OWNER; - else if (!((flags & DBUS_SERVICE_FLAG_REPLACE_EXISTING))) - service_reply = DBUS_SERVICE_REPLY_SERVICE_EXISTS; - else if (bus_service_get_prohibit_replacement (service)) - { - /* Queue the connection */ - if (!bus_service_add_owner (service, connection, - transaction, error)) - goto out; - - service_reply = DBUS_SERVICE_REPLY_IN_QUEUE; - } - else - { - /* Replace the current owner */ - - /* We enqueue the new owner and remove the first one because - * that will cause ServiceAcquired and ServiceLost messages to - * be sent. - */ - - /* FIXME this is broken, if the remove_owner fails - * we don't undo the add_owner - * (easiest fix is probably to move all this to - * services.c and have a single routine for it) - */ - - if (!bus_service_add_owner (service, connection, - transaction, error)) - goto out; - - if (!bus_service_remove_owner (service, old_owner, - transaction, error)) - goto out; - - _dbus_assert (connection == bus_service_get_primary_owner (service)); - service_reply = DBUS_SERVICE_REPLY_PRIMARY_OWNER; - } - if (!dbus_message_append_args (reply, DBUS_TYPE_UINT32, service_reply, 0)) + if (!dbus_message_append_args (reply, DBUS_TYPE_UINT32, service_reply, DBUS_TYPE_INVALID)) { BUS_SET_OOM (error); goto out; diff --git a/bus/services.c b/bus/services.c index 3a7a0756..dfc3ed08 100644 --- a/bus/services.c +++ b/bus/services.c @@ -33,6 +33,8 @@ struct BusService { + int refcount; + BusRegistry *registry; char *name; DBusList *owners; @@ -142,7 +144,8 @@ bus_registry_ensure (BusRegistry *registry, } service->registry = registry; - + service->refcount = 1; + if (!_dbus_string_copy_data (service_name, &service->name)) { _dbus_mem_pool_dealloc (registry->service_pool, service); @@ -152,24 +155,21 @@ bus_registry_ensure (BusRegistry *registry, if (!bus_driver_send_service_created (service->name, transaction, error)) { - dbus_free (service->name); - _dbus_mem_pool_dealloc (registry->service_pool, service); + bus_service_unref (service); return NULL; } if (!bus_activation_service_created (bus_context_get_activation (registry->context), service->name, transaction, error)) { - dbus_free (service->name); - _dbus_mem_pool_dealloc (registry->service_pool, service); + bus_service_unref (service); return NULL; } if (!bus_service_add_owner (service, owner_if_created, transaction, error)) { - dbus_free (service->name); - _dbus_mem_pool_dealloc (registry->service_pool, service); + bus_service_unref (service); return NULL; } @@ -177,11 +177,7 @@ bus_registry_ensure (BusRegistry *registry, service->name, service)) { - bus_connection_remove_owned_service (owner_if_created, - service); - _dbus_list_clear (&service->owners); - dbus_free (service->name); - _dbus_mem_pool_dealloc (registry->service_pool, service); + /* The add_owner gets reverted on transaction cancel */ BUS_SET_OOM (error); return NULL; } @@ -250,6 +246,209 @@ bus_registry_list_services (BusRegistry *registry, } 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; + DBusConnection *current_owner; + BusService *service; + + retval = FALSE; + + if (_dbus_string_get_length (service_name) == 0) + { + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Zero-length service name is not allowed"); + + _dbus_verbose ("Attempt to acquire zero-length 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_ACCESS_DENIED, + "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; + } + + service = bus_registry_lookup (registry, service_name); + + if (service != NULL) + old_owner = bus_service_get_primary_owner (service); + else + old_owner = NULL; + + if (service == NULL) + { + service = bus_registry_ensure (registry, + service_name, connection, transaction, error); + if (service == NULL) + goto out; + } + + current_owner = bus_service_get_primary_owner (service); + + if (old_owner == NULL) + { + _dbus_assert (current_owner == connection); + + bus_service_set_prohibit_replacement (service, + (flags & DBUS_SERVICE_FLAG_PROHIBIT_REPLACEMENT)); + + *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER; + } + else if (old_owner == connection) + *result = DBUS_SERVICE_REPLY_ALREADY_OWNER; + else if (!((flags & DBUS_SERVICE_FLAG_REPLACE_EXISTING))) + *result = DBUS_SERVICE_REPLY_SERVICE_EXISTS; + else if (bus_service_get_prohibit_replacement (service)) + { + /* Queue the connection */ + if (!bus_service_add_owner (service, connection, + transaction, error)) + goto out; + + *result = DBUS_SERVICE_REPLY_IN_QUEUE; + } + else + { + /* Replace the current owner */ + + /* We enqueue the new owner and remove the first one because + * that will cause ServiceAcquired and ServiceLost messages to + * be sent. + */ + + if (!bus_service_add_owner (service, connection, + transaction, error)) + goto out; + + if (!bus_service_remove_owner (service, old_owner, + transaction, error)) + goto out; + + _dbus_assert (connection == bus_service_get_primary_owner (service)); + *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER; + } + + retval = TRUE; + + out: + return retval; +} + +static void +bus_service_unlink_owner (BusService *service, + DBusConnection *owner) +{ + _dbus_list_remove_last (&service->owners, owner); + bus_connection_remove_owned_service (owner, service); +} + +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); +} + +typedef struct +{ + DBusConnection *connection; + BusService *service; +} 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->connection); + + 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->connection); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_cancel_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + DBusConnection *connection) +{ + OwnershipCancelData *d; + + d = dbus_new (OwnershipCancelData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->connection = connection; + + if (!bus_transaction_add_cancel_hook (transaction, cancel_ownership, d, + free_ownership_cancel_data)) + { + dbus_free (d); + return FALSE; + } + + bus_service_ref (d->service); + dbus_connection_ref (d->connection); + + return TRUE; +} + +/* this function is self-cancelling if you cancel the transaction */ +dbus_bool_t bus_service_add_owner (BusService *service, DBusConnection *owner, BusTransaction *transaction, @@ -279,10 +478,147 @@ bus_service_add_owner (BusService *service, BUS_SET_OOM (error); return FALSE; } + + if (!add_cancel_ownership_to_transaction (transaction, + service, + owner)) + { + bus_service_unlink_owner (service, owner); + BUS_SET_OOM (error); + return FALSE; + } return TRUE; } +typedef struct +{ + DBusConnection *connection; + BusService *service; + DBusConnection *before_connection; /* restore to position before this connection in owners list */ + DBusList *connection_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->connection_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_connection) + break; + + link = _dbus_list_get_next_link (&d->service->owners, link); + } + + _dbus_list_insert_before_link (&d->service->owners, link, d->connection_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->connection, d->service_link); + + d->hash_entry = NULL; + d->service_link = NULL; + d->connection_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->connection_link) + _dbus_list_free_link (d->connection_link); + if (d->hash_entry) + _dbus_hash_table_free_preallocated_entry (d->service->registry->service_hash, + d->hash_entry); + + dbus_connection_unref (d->connection); + bus_service_unref (d->service); + + dbus_free (d); +} + +static dbus_bool_t +add_restore_ownership_to_transaction (BusTransaction *transaction, + BusService *service, + DBusConnection *connection) +{ + OwnershipRestoreData *d; + DBusList *link; + + d = dbus_new (OwnershipRestoreData, 1); + if (d == NULL) + return FALSE; + + d->service = service; + d->connection = connection; + d->service_link = _dbus_list_alloc_link (service); + d->connection_link = _dbus_list_alloc_link (connection); + d->hash_entry = _dbus_hash_table_preallocate_entry (service->registry->service_hash); + + bus_service_ref (d->service); + dbus_connection_ref (d->connection); + + d->before_connection = NULL; + link = _dbus_list_get_first_link (&service->owners); + while (link != NULL) + { + if (link->data == connection) + { + link = _dbus_list_get_next_link (&service->owners, link); + + if (link) + d->before_connection = link->data; + + break; + } + + link = _dbus_list_get_next_link (&service->owners, link); + } + + if (d->service_link == NULL || + d->connection_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; +} + +/* this function is self-cancelling if you cancel the transaction */ dbus_bool_t bus_service_remove_owner (BusService *service, DBusConnection *owner, @@ -309,7 +645,6 @@ bus_service_remove_owner (BusService *service, } else if (_dbus_list_length_is_one (&service->owners)) { - /* We are the only owner - send service deleted */ if (!bus_driver_send_service_deleted (service->name, transaction, error)) return FALSE; @@ -321,31 +656,52 @@ bus_service_remove_owner (BusService *service, _dbus_assert (link != NULL); link = _dbus_list_get_next_link (&service->owners, link); - if (link != NULL) - { - /* This will be our new owner */ - if (!bus_driver_send_service_acquired (link->data, - service->name, - transaction, - error)) - return FALSE; - } + _dbus_assert (link != NULL); + + /* This will be our new owner */ + if (!bus_driver_send_service_acquired (link->data, + service->name, + transaction, + error)) + return FALSE; + } + + if (!add_restore_ownership_to_transaction (transaction, service, owner)) + { + BUS_SET_OOM (error); + return FALSE; } - _dbus_list_remove_last (&service->owners, owner); - bus_connection_remove_owned_service (owner, service); + bus_service_unlink_owner (service, owner); if (service->owners == NULL) + bus_service_unlink (service); + + return TRUE; +} + +void +bus_service_ref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount += 1; +} + +void +bus_service_unref (BusService *service) +{ + _dbus_assert (service->refcount > 0); + + service->refcount -= 1; + + if (service->refcount == 0) { - /* Delete service (already sent message that it was deleted above) */ - _dbus_hash_table_remove_string (service->registry->service_hash, - service->name); + _dbus_assert (service->owners == NULL); dbus_free (service->name); _dbus_mem_pool_dealloc (service->registry->service_pool, service); } - - return TRUE; } DBusConnection* diff --git a/bus/services.h b/bus/services.h index aba2989a..bed950c3 100644 --- a/bus/services.h +++ b/bus/services.h @@ -32,38 +32,47 @@ typedef void (* BusServiceForeachFunction) (BusService *service, void *data); -BusRegistry* bus_registry_new (BusContext *context); -void bus_registry_ref (BusRegistry *registry); -void bus_registry_unref (BusRegistry *registry); -BusService* bus_registry_lookup (BusRegistry *registry, - const DBusString *service_name); -BusService* bus_registry_ensure (BusRegistry *registry, - const DBusString *service_name, - DBusConnection *owner_if_created, - BusTransaction *transaction, - DBusError *error); -void bus_registry_foreach (BusRegistry *registry, - BusServiceForeachFunction function, - void *data); -dbus_bool_t bus_registry_list_services (BusRegistry *registry, - char ***listp, - int *array_len); +BusRegistry* bus_registry_new (BusContext *context); +void bus_registry_ref (BusRegistry *registry); +void bus_registry_unref (BusRegistry *registry); +BusService* bus_registry_lookup (BusRegistry *registry, + const DBusString *service_name); +BusService* bus_registry_ensure (BusRegistry *registry, + const DBusString *service_name, + DBusConnection *owner_if_created, + BusTransaction *transaction, + DBusError *error); +void bus_registry_foreach (BusRegistry *registry, + BusServiceForeachFunction function, + void *data); +dbus_bool_t bus_registry_list_services (BusRegistry *registry, + char ***listp, + int *array_len); +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); +void bus_service_ref (BusService *service); +void bus_service_unref (BusService *service); +dbus_bool_t bus_service_add_owner (BusService *service, + DBusConnection *owner, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_service_remove_owner (BusService *service, + DBusConnection *owner, + BusTransaction *transaction, + DBusError *error); +dbus_bool_t bus_service_has_owner (BusService *service, + DBusConnection *owner); +DBusConnection* bus_service_get_primary_owner (BusService *service); +void bus_service_set_prohibit_replacement (BusService *service, + dbus_bool_t prohibit_replacement); +dbus_bool_t bus_service_get_prohibit_replacement (BusService *service); +const char* bus_service_get_name (BusService *service); -dbus_bool_t bus_service_add_owner (BusService *service, - DBusConnection *owner, - BusTransaction *transaction, - DBusError *error); -dbus_bool_t bus_service_remove_owner (BusService *service, - DBusConnection *owner, - BusTransaction *transaction, - DBusError *error); -dbus_bool_t bus_service_has_owner (BusService *service, - DBusConnection *owner); -DBusConnection* bus_service_get_primary_owner (BusService *service); -void bus_service_set_prohibit_replacement (BusService *service, - dbus_bool_t prohibit_replacement); -dbus_bool_t bus_service_get_prohibit_replacement (BusService *service); -const char* bus_service_get_name (BusService *service); #endif /* BUS_SERVICES_H */ |