From 93f433a17a0aabff91a0384bf9c4f99c9cf30ae1 Mon Sep 17 00:00:00 2001 From: Richard Hult Date: Tue, 16 Mar 2004 18:00:35 +0000 Subject: 2004-03-16 Richard Hult * bus/activation.c: (bus_activation_service_created), (bus_activation_send_pending_auto_activation_messages), (bus_activation_activate_service): * bus/activation.h: * bus/dispatch.c: (bus_dispatch), (check_nonexistent_service_auto_activation), (check_service_auto_activated), (check_segfault_service_auto_activation), (check_existent_service_auto_activation), (bus_dispatch_test): * bus/driver.c: (bus_driver_handle_activate_service): * bus/services.c: (bus_registry_acquire_service): * dbus/dbus-message.c: (dbus_message_set_auto_activation), (dbus_message_get_auto_activation): * dbus/dbus-message.h: * dbus/dbus-protocol.h: Implement auto-activation. --- bus/activation.c | 101 ++++++++-- bus/activation.h | 29 +-- bus/dispatch.c | 564 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- bus/driver.c | 2 +- bus/services.c | 7 +- 5 files changed, 671 insertions(+), 32 deletions(-) (limited to 'bus') diff --git a/bus/activation.c b/bus/activation.c index e53fa229..e286ba9d 100644 --- a/bus/activation.c +++ b/bus/activation.c @@ -76,6 +76,8 @@ struct BusPendingActivationEntry { DBusMessage *activation_message; DBusConnection *connection; + + dbus_bool_t auto_activation; }; typedef struct @@ -904,30 +906,90 @@ bus_activation_service_created (BusActivation *activation, if (dbus_connection_get_is_connected (entry->connection)) { - message = dbus_message_new_method_return (entry->activation_message); - if (!message) - { - BUS_SET_OOM (error); - goto error; - } - - if (!dbus_message_append_args (message, - DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED, - DBUS_TYPE_INVALID)) + /* Only send activation replies to regular activation requests. */ + if (!entry->auto_activation) { + message = dbus_message_new_method_return (entry->activation_message); + if (!message) + { + BUS_SET_OOM (error); + goto error; + } + + if (!dbus_message_append_args (message, + DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + goto error; + } + + if (!bus_transaction_send_from_driver (transaction, entry->connection, message)) + { + dbus_message_unref (message); + BUS_SET_OOM (error); + goto error; + } + dbus_message_unref (message); - BUS_SET_OOM (error); - goto error; } - - if (!bus_transaction_send_from_driver (transaction, entry->connection, message)) + } + + link = next; + } + + return TRUE; + + error: + return FALSE; +} + +dbus_bool_t +bus_activation_send_pending_auto_activation_messages (BusActivation *activation, + BusService *service, + BusTransaction *transaction, + DBusError *error) +{ + BusPendingActivation *pending_activation; + DBusList *link; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + /* Check if it's a pending activation */ + pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, + bus_service_get_name (service)); + + if (!pending_activation) + return TRUE; + + link = _dbus_list_get_first_link (&pending_activation->entries); + while (link != NULL) + { + BusPendingActivationEntry *entry = link->data; + DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link); + + if (entry->auto_activation && dbus_connection_get_is_connected (entry->connection)) + { + DBusConnection *addressed_recipient; + + addressed_recipient = bus_service_get_primary_owner (service); + + /* Check the security policy, which has the side-effect of adding an + * expected pending reply. + */ + if (!bus_context_check_security_policy (activation->context, transaction, + entry->connection, + addressed_recipient, + addressed_recipient, + entry->activation_message, error)) + goto error; + + if (!bus_transaction_send (transaction, addressed_recipient, entry->activation_message)) { - dbus_message_unref (message); BUS_SET_OOM (error); goto error; } - - dbus_message_unref (message); } link = next; @@ -940,7 +1002,7 @@ bus_activation_service_created (BusActivation *activation, goto error; } - _dbus_hash_table_remove_string (activation->pending_activations, service_name); + _dbus_hash_table_remove_string (activation->pending_activations, bus_service_get_name (service)); return TRUE; @@ -1225,6 +1287,7 @@ dbus_bool_t bus_activation_activate_service (BusActivation *activation, DBusConnection *connection, BusTransaction *transaction, + dbus_bool_t auto_activation, DBusMessage *activation_message, const char *service_name, DBusError *error) @@ -1300,6 +1363,8 @@ bus_activation_activate_service (BusActivation *activation, return FALSE; } + pending_activation_entry->auto_activation = auto_activation; + pending_activation_entry->activation_message = activation_message; dbus_message_ref (activation_message); pending_activation_entry->connection = connection; diff --git a/bus/activation.h b/bus/activation.h index 76a1b265..fbac5d1f 100644 --- a/bus/activation.h +++ b/bus/activation.h @@ -29,21 +29,28 @@ #include "bus.h" BusActivation* bus_activation_new (BusContext *context, - const DBusString *address, - DBusList **directories, - DBusError *error); + const DBusString *address, + DBusList **directories, + DBusError *error); BusActivation* bus_activation_ref (BusActivation *activation); void bus_activation_unref (BusActivation *activation); dbus_bool_t bus_activation_activate_service (BusActivation *activation, - DBusConnection *connection, - BusTransaction *transaction, - DBusMessage *activation_message, - const char *service_name, - DBusError *error); + DBusConnection *connection, + BusTransaction *transaction, + dbus_bool_t auto_activation, + DBusMessage *activation_message, + const char *service_name, + DBusError *error); dbus_bool_t bus_activation_service_created (BusActivation *activation, - const char *service_name, - BusTransaction *transaction, - DBusError *error); + const char *service_name, + BusTransaction *transaction, + DBusError *error); + +dbus_bool_t bus_activation_send_pending_auto_activation_messages (BusActivation *activation, + BusService *service, + BusTransaction *transaction, + DBusError *error); + #endif /* BUS_ACTIVATION_H */ diff --git a/bus/dispatch.c b/bus/dispatch.c index b4d782ee..b7191d7e 100644 --- a/bus/dispatch.c +++ b/bus/dispatch.c @@ -3,6 +3,7 @@ * * Copyright (C) 2003 CodeFactory AB * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2004 Imendio HB * * Licensed under the Academic Free License version 2.0 * @@ -26,6 +27,7 @@ #include "connection.h" #include "driver.h" #include "services.h" +#include "activation.h" #include "utils.h" #include "bus.h" #include "signals.h" @@ -257,7 +259,27 @@ bus_dispatch (DBusConnection *connection, _dbus_string_init_const (&service_string, service_name); service = bus_registry_lookup (registry, &service_string); - if (service == NULL) + if (service == NULL && dbus_message_get_auto_activation (message)) + { + BusActivation *activation; + + /* We can't do the security policy check here, since the addressed + * recipient service doesn't exist yet. We do it before sending the + * message after the service has been created. + */ + activation = bus_connection_get_activation (connection); + + if (!bus_activation_activate_service (activation, connection, transaction, TRUE, + message, service_name, &error)) + { + _DBUS_ASSERT_ERROR_IS_SET (&error); + _dbus_verbose ("bus_activation_activate_service() failed\n"); + goto out; + } + + goto out; + } + else if (service == NULL) { dbus_set_error (&error, DBUS_ERROR_SERVICE_DOES_NOT_EXIST, @@ -378,6 +400,8 @@ bus_dispatch_remove_connection (DBusConnection *connection) #ifdef DBUS_BUILD_TESTS +#include + typedef dbus_bool_t (* Check1Func) (BusContext *context); typedef dbus_bool_t (* Check2Func) (BusContext *context, DBusConnection *connection); @@ -1170,6 +1194,104 @@ check_nonexistent_service_activation (BusContext *context, return retval; } +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_nonexistent_service_auto_activation (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + + dbus_error_init (&error); + + message = dbus_message_new_method_call (NONEXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_activation (message, TRUE); + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected\n"); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_ORG_FREEDESKTOP_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_ACTIVATE_SERVICE_NOT_FOUND)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate %s\n", + NONEXISTENT_SERVICE_NAME); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + static dbus_bool_t check_base_service_activated (BusContext *context, DBusConnection *connection, @@ -1397,6 +1519,94 @@ check_service_activated (BusContext *context, return retval; } +static dbus_bool_t +check_service_auto_activated (BusContext *context, + DBusConnection *connection, + const char *activated_name, + const char *base_service_name, + DBusMessage *initial_message) +{ + DBusMessage *message; + dbus_bool_t retval; + DBusError error; + + retval = FALSE; + + dbus_error_init (&error); + + message = initial_message; + dbus_message_ref (message); + + if (dbus_message_is_signal (message, + DBUS_INTERFACE_ORG_FREEDESKTOP_DBUS, + "ServiceCreated")) + { + char *service_name; + CheckServiceCreatedData scd; + + reget_service_name_arg: + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_INVALID)) + { + if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) + { + dbus_error_free (&error); + _dbus_wait_for_memory (); + goto reget_service_name_arg; + } + else + { + _dbus_warn ("Message %s doesn't have a service name: %s\n", + "ServiceCreated", + error.message); + dbus_error_free (&error); + goto out; + } + } + + if (strcmp (service_name, activated_name) != 0) + { + _dbus_warn ("Expected to see service %s created, saw %s instead\n", + activated_name, service_name); + dbus_free (service_name); + goto out; + } + + scd.skip_connection = connection; + scd.failed = FALSE; + scd.expected_service_name = service_name; + bus_test_clients_foreach (check_service_created_foreach, + &scd); + + dbus_free (service_name); + + if (scd.failed) + goto out; + + /* Note that this differs from regular activation in that we don't get a + * reply to ActivateService here. + */ + + dbus_message_unref (message); + message = NULL; + } + else + { + warn_unexpected (connection, message, "ServiceCreated for the activated name"); + + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + static dbus_bool_t check_service_deactivated (BusContext *context, DBusConnection *connection, @@ -1974,6 +2184,340 @@ check_segfault_service_activation (BusContext *context, return retval; } + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_segfault_service_auto_activation (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + + dbus_error_init (&error); + + dbus_error_init (&error); + + message = dbus_message_new_method_call ("org.freedesktop.DBus.TestSuiteSegfaultService", + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_activation (message, TRUE); + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + block_connection_until_message_from_bus (context, connection); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected\n"); + return TRUE; + } + + retval = FALSE; + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive a reply to %s %d on %p\n", + "Echo message (auto activation)", serial, connection); + goto out; + } + + verbose_message_received (connection, message); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_ORG_FREEDESKTOP_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, + DBUS_ERROR_NO_MEMORY)) + { + ; /* good, this is a valid response */ + } + else if (dbus_message_is_error (message, + DBUS_ERROR_SPAWN_CHILD_SIGNALED)) + { + ; /* good, this is expected also */ + } + else + { + warn_unexpected (connection, message, "not this error"); + + goto out; + } + } + else + { + _dbus_warn ("Did not expect to successfully activate segfault service\n"); + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + return retval; +} + +#define TEST_ECHO_MESSAGE "Test echo message" + +/* returns TRUE if the correct thing happens, + * but the correct thing may include OOM errors. + */ +static dbus_bool_t +check_existent_service_auto_activation (BusContext *context, + DBusConnection *connection) +{ + DBusMessage *message; + dbus_uint32_t serial; + dbus_bool_t retval; + DBusError error; + char *base_service; + + base_service = NULL; + + dbus_error_init (&error); + + message = dbus_message_new_method_call (EXISTENT_SERVICE_NAME, + "/org/freedesktop/TestSuite", + "org.freedesktop.TestSuite", + "Echo"); + + if (message == NULL) + return TRUE; + + dbus_message_set_auto_activation (message, TRUE); + + if (!dbus_message_append_args (message, + DBUS_TYPE_STRING, TEST_ECHO_MESSAGE, + DBUS_TYPE_INVALID)) + { + dbus_message_unref (message); + return TRUE; + } + + if (!dbus_connection_send (connection, message, &serial)) + { + dbus_message_unref (message); + return TRUE; + } + + dbus_message_unref (message); + message = NULL; + + bus_test_run_everything (context); + + /* now wait for the message bus to hear back from the activated + * service. + */ + block_connection_until_message_from_bus (context, connection); + bus_test_run_everything (context); + + if (!dbus_connection_get_is_connected (connection)) + { + _dbus_verbose ("connection was disconnected\n"); + return TRUE; + } + + retval = FALSE; + + /* Should get ServiceCreated for the base service, or an error. */ + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after auto activation %d on %p\n", + serial, connection); + goto out; + } + + verbose_message_received (connection, message); + _dbus_verbose (" (after sending %s)\n", "ActivateService"); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) + { + if (!dbus_message_has_sender (message, DBUS_SERVICE_ORG_FREEDESKTOP_DBUS)) + { + _dbus_warn ("Message has wrong sender %s\n", + dbus_message_get_sender (message) ? + dbus_message_get_sender (message) : "(none)"); + goto out; + } + + if (dbus_message_is_error (message, DBUS_ERROR_NO_MEMORY) || + dbus_message_is_error (message, DBUS_ERROR_SPAWN_CHILD_EXITED) || + dbus_message_is_error (message, DBUS_ERROR_TIMED_OUT)) + { + ; /* good, those are expected */ + retval = TRUE; + goto out; + } + else + { + _dbus_warn ("Did not expect error %s\n", + dbus_message_get_error_name (message)); + goto out; + } + } + else + { + dbus_bool_t got_service_deleted; + dbus_bool_t got_error; + + if (!check_base_service_activated (context, connection, + message, &base_service)) + goto out; + + dbus_message_unref (message); + message = NULL; + + /* We may need to block here for the test service to exit or finish up */ + block_connection_until_message_from_bus (context, connection); + + /* Should get ServiceCreated for the activated service name, + * ServiceDeleted on the base service name, or an error. + */ + message = dbus_connection_borrow_message (connection); + if (message == NULL) + { + _dbus_warn ("Did not receive any messages after base service creation notification\n"); + goto out; + } + + got_service_deleted = dbus_message_is_signal (message, + DBUS_INTERFACE_ORG_FREEDESKTOP_DBUS, + "ServiceDeleted"); + got_error = dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR; + + dbus_connection_return_message (connection, message); + message = NULL; + + if (got_error) + { + if (!check_got_error (context, connection, + DBUS_ERROR_SPAWN_CHILD_EXITED, + DBUS_ERROR_NO_MEMORY, + NULL)) + 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) + { + /* The service started up and got a base address, but then + * failed to register under EXISTENT_SERVICE_NAME + */ + CheckServiceDeletedData csdd; + + csdd.expected_service_name = base_service; + csdd.failed = FALSE; + bus_test_clients_foreach (check_service_deleted_foreach, + &csdd); + + if (csdd.failed) + goto out; + + /* Now we should get an error about the service exiting + * if we didn't get it before. + */ + if (!got_error) + { + 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, + NULL)) + goto out; + } + } + else + { + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message we just put back! should have been a ServiceCreated\n"); + goto out; + } + + /* Check that ServiceCreated was correctly received */ + if (!check_service_auto_activated (context, connection, EXISTENT_SERVICE_NAME, + base_service, message)) + goto out; + + dbus_message_unref (message); + message = NULL; + } + + /* Note: if this test is run in OOM mode, it will block when the bus + * doesn't send a reply due to OOM. + */ + block_connection_until_message_from_bus (context, connection); + + message = pop_message_waiting_for_memory (connection); + if (message == NULL) + { + _dbus_warn ("Failed to pop message! Should have been reply from echo message\n"); + goto out; + } + + if (dbus_message_get_reply_serial (message) != serial) + { + _dbus_warn ("Wrong reply serial\n"); + goto out; + } + + dbus_message_unref (message); + message = NULL; + + if (!check_send_exit_to_service (context, connection, + EXISTENT_SERVICE_NAME, + base_service)) + goto out; + } + + retval = TRUE; + + out: + if (message) + dbus_message_unref (message); + + if (base_service) + dbus_free (base_service); + + return retval; +} + typedef struct { Check1Func func; @@ -2126,6 +2670,24 @@ bus_dispatch_test (const DBusString *test_data_dir) check2_try_iterations (context, foo, "existent_service_activation", check_existent_service_activation); + check2_try_iterations (context, foo, "nonexistent_service_auto_activation", + check_nonexistent_service_auto_activation); + + check2_try_iterations (context, foo, "segfault_service_auto_activation", + check_segfault_service_auto_activation); + +#if 0 + /* Note: need to resolve some issues with the testing code in order to run + * this in oom (handle that we sometimes don't get replies back from the bus + * when oom happens, without blocking the test). + */ + check2_try_iterations (context, foo, "existent_service_auto_activation", + check_existent_service_auto_activation); +#endif + + if (!check_existent_service_auto_activation (context, foo)) + _dbus_assert_not_reached ("existent service auto activation failed"); + _dbus_verbose ("Disconnecting foo, bar, and baz\n"); kill_client_connection_unchecked (foo); diff --git a/bus/driver.c b/bus/driver.c index 1679a876..3ffae2e0 100644 --- a/bus/driver.c +++ b/bus/driver.c @@ -587,7 +587,7 @@ bus_driver_handle_activate_service (DBusConnection *connection, retval = FALSE; - if (!bus_activation_activate_service (activation, connection, transaction, + if (!bus_activation_activate_service (activation, connection, transaction, FALSE, message, name, error)) { _DBUS_ASSERT_ERROR_IS_SET (error); diff --git a/bus/services.c b/bus/services.c index ae7b7838..c5a6ded8 100644 --- a/bus/services.c +++ b/bus/services.c @@ -262,6 +262,7 @@ bus_registry_acquire_service (BusRegistry *registry, DBusConnection *current_owner; BusClientPolicy *policy; BusService *service; + BusActivation *activation; retval = FALSE; @@ -376,7 +377,11 @@ bus_registry_acquire_service (BusRegistry *registry, *result = DBUS_SERVICE_REPLY_PRIMARY_OWNER; } - retval = TRUE; + activation = bus_context_get_activation (registry->context); + retval = bus_activation_send_pending_auto_activation_messages (activation, + service, + transaction, + error); out: return retval; -- cgit