#include <dbus/dbus-internals.h>
#include <dbus/dbus-hash.h>
#include <dbus/dbus-list.h>
+#include <dbus/dbus-spawn.h>
+#include <dbus/dbus-timeout.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
DBusHashTable *pending_activations;
char *server_address;
BusContext *context;
+ int n_pending_activations; /**< This is in fact the number of BusPendingActivationEntry,
+ * i.e. number of pending activation requests, not pending
+ * activations per se
+ */
};
typedef struct
typedef struct
{
+ int refcount;
+ BusActivation *activation;
char *service_name;
DBusList *entries;
+ int n_entries;
+ DBusBabysitter *babysitter;
+ DBusTimeout *timeout;
+ unsigned int timeout_added : 1;
} BusPendingActivation;
static void
if (entry->connection)
dbus_connection_unref (entry->connection);
-
+
dbus_free (entry);
}
static void
-bus_pending_activation_free (BusPendingActivation *activation)
+handle_timeout_callback (DBusTimeout *timeout,
+ void *data)
+{
+ BusPendingActivation *pending_activation = data;
+
+ while (!dbus_timeout_handle (pending_activation->timeout))
+ _dbus_wait_for_memory ();
+}
+
+static void
+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 (!activation)
+ if (pending_activation == NULL) /* hash table requires this */
return;
- dbus_free (activation->service_name);
+ _dbus_assert (pending_activation->refcount > 0);
+ pending_activation->refcount -= 1;
- link = _dbus_list_get_first_link (&activation->entries);
+ if (pending_activation->refcount > 0)
+ return;
+
+ if (pending_activation->timeout_added)
+ {
+ _dbus_loop_remove_timeout (bus_context_get_loop (pending_activation->activation->context),
+ pending_activation->timeout,
+ handle_timeout_callback, pending_activation);
+ pending_activation->timeout_added = FALSE;
+ }
+
+ if (pending_activation->timeout)
+ _dbus_timeout_unref (pending_activation->timeout);
+
+ if (pending_activation->babysitter)
+ {
+ if (!_dbus_babysitter_set_watch_functions (pending_activation->babysitter,
+ NULL, NULL, NULL,
+ pending_activation->babysitter,
+ NULL))
+ _dbus_assert_not_reached ("setting watch functions to NULL failed");
+
+ _dbus_babysitter_unref (pending_activation->babysitter);
+ }
+
+ dbus_free (pending_activation->service_name);
+
+ link = _dbus_list_get_first_link (&pending_activation->entries);
while (link != NULL)
{
bus_pending_activation_entry_free (entry);
- link = _dbus_list_get_next_link (&activation->entries, link);
+ link = _dbus_list_get_next_link (&pending_activation->entries, link);
}
- _dbus_list_clear (&activation->entries);
+ _dbus_list_clear (&pending_activation->entries);
+
+ pending_activation->activation->n_pending_activations -=
+ pending_activation->n_entries;
+
+ _dbus_assert (pending_activation->activation->n_pending_activations >= 0);
- dbus_free (activation);
+ dbus_free (pending_activation);
}
static void
activation->refcount = 1;
activation->context = context;
+ activation->n_pending_activations = 0;
if (!_dbus_string_copy_data (address, &activation->server_address))
{
}
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)
{
}
}
+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,
goto error;
}
- if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
- !dbus_message_append_args (message,
+ if (!dbus_message_append_args (message,
DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED,
0))
{
goto error;
}
- if (!bus_transaction_send_message (transaction, entry->connection, message))
+ 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_pending_activation_entry_free (entry);
-
- _dbus_list_remove_link (&pending_activation->entries, link);
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;
}
+/**
+ * FIXME @todo the error messages here would ideally be preallocated
+ * so we don't need to allocate memory to send them.
+ * Using the usual tactic, prealloc an OOM message, then
+ * if we can't alloc the real error send the OOM error instead.
+ */
+static dbus_bool_t
+try_send_activation_failure (BusPendingActivation *pending_activation,
+ const DBusError *how)
+{
+ BusActivation *activation;
+ DBusList *link;
+ BusTransaction *transaction;
+
+ activation = pending_activation->activation;
+
+ transaction = bus_transaction_new (activation->context);
+ if (transaction == NULL)
+ return FALSE;
+
+ 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 (dbus_connection_get_is_connected (entry->connection))
+ {
+ if (!bus_transaction_send_error_reply (transaction,
+ entry->connection,
+ how,
+ entry->activation_message))
+ goto error;
+ }
+
+ link = next;
+ }
+
+ bus_transaction_execute_and_free (transaction);
+
+ return TRUE;
+
+ error:
+ if (transaction)
+ bus_transaction_cancel_and_free (transaction);
+ return FALSE;
+}
+
+/**
+ * Free the pending activation and send an error message to all the
+ * connections that were waiting for it.
+ */
+static void
+pending_activation_failed (BusPendingActivation *pending_activation,
+ const DBusError *how)
+{
+ /* FIXME use preallocated OOM messages instead of bus_wait_for_memory() */
+ while (!try_send_activation_failure (pending_activation, how))
+ _dbus_wait_for_memory ();
+
+ /* Destroy this pending activation */
+ _dbus_hash_table_remove_string (pending_activation->activation->pending_activations,
+ pending_activation->service_name);
+}
+
+static dbus_bool_t
+babysitter_watch_callback (DBusWatch *watch,
+ unsigned int condition,
+ void *data)
+{
+ BusPendingActivation *pending_activation = data;
+ dbus_bool_t retval;
+ DBusBabysitter *babysitter;
+
+ babysitter = pending_activation->babysitter;
+
+ _dbus_babysitter_ref (babysitter);
+
+ retval = dbus_watch_handle (watch, condition);
+
+ /* FIXME this is broken in the same way that
+ * connection watches used to be; there should be
+ * a separate callback for status change, instead
+ * of doing "if we handled a watch status might
+ * have changed"
+ *
+ * Fixing this lets us move dbus_watch_handle
+ * calls into dbus-mainloop.c
+ */
+
+ if (_dbus_babysitter_get_child_exited (babysitter))
+ {
+ DBusError error;
+
+ dbus_error_init (&error);
+ _dbus_babysitter_set_child_exit_error (babysitter, &error);
+
+ /* Destroys the pending activation */
+ pending_activation_failed (pending_activation, &error);
+
+ dbus_error_free (&error);
+ }
+
+ _dbus_babysitter_unref (babysitter);
+
+ return retval;
+}
+
+static dbus_bool_t
+add_babysitter_watch (DBusWatch *watch,
+ void *data)
+{
+ BusPendingActivation *pending_activation = data;
+
+ return _dbus_loop_add_watch (bus_context_get_loop (pending_activation->activation->context),
+ watch, babysitter_watch_callback, pending_activation,
+ NULL);
+}
+
+static void
+remove_babysitter_watch (DBusWatch *watch,
+ void *data)
+{
+ BusPendingActivation *pending_activation = data;
+
+ _dbus_loop_remove_watch (bus_context_get_loop (pending_activation->activation->context),
+ watch, babysitter_watch_callback, pending_activation);
+}
+
+static dbus_bool_t
+pending_activation_timed_out (void *data)
+{
+ BusPendingActivation *pending_activation = data;
+ DBusError error;
+
+ /* Kill the spawned process, since it sucks
+ * (not sure this is what we want to do, but
+ * may as well try it for now)
+ */
+ _dbus_babysitter_kill_child (pending_activation->babysitter);
+
+ dbus_error_init (&error);
+
+ dbus_set_error (&error, DBUS_ERROR_TIMED_OUT,
+ "Activation of %s timed out",
+ pending_activation->service_name);
+
+ pending_activation_failed (pending_activation, &error);
+
+ dbus_error_free (&error);
+
+ return TRUE;
+}
+
+static void
+cancel_pending (void *data)
+{
+ BusPendingActivation *pending_activation = data;
+
+ _dbus_verbose ("Canceling pending activation of %s\n",
+ pending_activation->service_name);
+
+ if (pending_activation->babysitter)
+ _dbus_babysitter_kill_child (pending_activation->babysitter);
+
+ _dbus_hash_table_remove_string (pending_activation->activation->pending_activations,
+ pending_activation->service_name);
+}
+
+static void
+free_pending_cancel_data (void *data)
+{
+ BusPendingActivation *pending_activation = data;
+
+ bus_pending_activation_unref (pending_activation);
+}
+
+static dbus_bool_t
+add_cancel_pending_to_transaction (BusTransaction *transaction,
+ BusPendingActivation *pending_activation)
+{
+ if (!bus_transaction_add_cancel_hook (transaction, cancel_pending,
+ pending_activation,
+ free_pending_cancel_data))
+ return FALSE;
+
+ bus_pending_activation_ref (pending_activation);
+
+ _dbus_verbose ("Saved pending activation to be canceled if the transaction fails\n");
+
+ return TRUE;
+}
+
dbus_bool_t
bus_activation_activate_service (BusActivation *activation,
DBusConnection *connection,
dbus_bool_t retval;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ if (activation->n_pending_activations >=
+ bus_context_get_max_pending_activations (activation->context))
+ {
+ dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
+ "The maximum number of pending activations has been reached, activation of %s failed",
+ service_name);
+ return FALSE;
+ }
entry = _dbus_hash_table_lookup_string (activation->entries, service_name);
_dbus_string_init_const (&service_str, service_name);
if (bus_registry_lookup (bus_context_get_registry (activation->context), &service_str) != NULL)
{
+ _dbus_verbose ("Service \"%s\" is already active\n", service_name);
+
message = dbus_message_new_reply (activation_message);
if (!message)
{
+ _dbus_verbose ("No memory to create reply to activate message\n");
BUS_SET_OOM (error);
return FALSE;
}
- if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
- !dbus_message_append_args (message,
+ if (!dbus_message_append_args (message,
DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ALREADY_ACTIVE,
0))
{
+ _dbus_verbose ("No memory to set args of reply to activate message\n");
BUS_SET_OOM (error);
dbus_message_unref (message);
return FALSE;
}
- retval = bus_transaction_send_message (transaction, connection, message);
+ retval = bus_transaction_send_from_driver (transaction, connection, message);
dbus_message_unref (message);
if (!retval)
- BUS_SET_OOM (error);
+ {
+ _dbus_verbose ("Failed to send reply\n");
+ BUS_SET_OOM (error);
+ }
return retval;
}
pending_activation_entry = dbus_new0 (BusPendingActivationEntry, 1);
if (!pending_activation_entry)
{
+ _dbus_verbose ("Failed to create pending activation entry\n");
BUS_SET_OOM (error);
return FALSE;
}
{
if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
{
+ _dbus_verbose ("Failed to append a new entry to pending activation\n");
+
BUS_SET_OOM (error);
bus_pending_activation_entry_free (pending_activation_entry);
-
return FALSE;
}
+
+ pending_activation->n_entries += 1;
+ pending_activation->activation->n_pending_activations += 1;
}
else
{
pending_activation = dbus_new0 (BusPendingActivation, 1);
if (!pending_activation)
{
+ _dbus_verbose ("Failed to create pending activation\n");
+
BUS_SET_OOM (error);
bus_pending_activation_entry_free (pending_activation_entry);
return FALSE;
}
+
+ pending_activation->activation = activation;
+ pending_activation->refcount = 1;
+
pending_activation->service_name = _dbus_strdup (service_name);
if (!pending_activation->service_name)
{
+ _dbus_verbose ("Failed to copy service name for pending activation\n");
+
+ BUS_SET_OOM (error);
+ bus_pending_activation_unref (pending_activation);
+ bus_pending_activation_entry_free (pending_activation_entry);
+ return FALSE;
+ }
+
+ pending_activation->timeout =
+ _dbus_timeout_new (bus_context_get_activation_timeout (activation->context),
+ pending_activation_timed_out,
+ pending_activation,
+ NULL);
+ if (!pending_activation->timeout)
+ {
+ _dbus_verbose ("Failed to create timeout for pending activation\n");
+
+ BUS_SET_OOM (error);
+ bus_pending_activation_unref (pending_activation);
+ bus_pending_activation_entry_free (pending_activation_entry);
+ return FALSE;
+ }
+
+ if (!_dbus_loop_add_timeout (bus_context_get_loop (activation->context),
+ pending_activation->timeout,
+ handle_timeout_callback,
+ pending_activation,
+ NULL))
+ {
+ _dbus_verbose ("Failed to add timeout for pending activation\n");
+
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;
}
+ pending_activation->timeout_added = TRUE;
+
if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
{
+ _dbus_verbose ("Failed to add entry to just-created pending activation\n");
+
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;
}
+
+ pending_activation->n_entries += 1;
+ pending_activation->activation->n_pending_activations += 1;
if (!_dbus_hash_table_insert_string (activation->pending_activations,
- pending_activation->service_name, pending_activation))
+ pending_activation->service_name,
+ pending_activation))
{
+ _dbus_verbose ("Failed to put pending activation in hash table\n");
+
BUS_SET_OOM (error);
- bus_pending_activation_free (pending_activation);
+ bus_pending_activation_unref (pending_activation);
return FALSE;
}
}
+
+ if (!add_cancel_pending_to_transaction (transaction, pending_activation))
+ {
+ _dbus_verbose ("Failed to add pending activation cancel hook to transaction\n");
+ BUS_SET_OOM (error);
+ _dbus_hash_table_remove_string (activation->pending_activations,
+ pending_activation->service_name);
+ return FALSE;
+ }
/* FIXME we need to support a full command line, not just a single
* argv[0]
argv[0] = entry->exec;
argv[1] = NULL;
- if (!_dbus_spawn_async_with_babysitter (NULL, argv,
+ if (!_dbus_spawn_async_with_babysitter (&pending_activation->babysitter, argv,
child_setup, activation,
error))
{
- _dbus_hash_table_remove_string (activation->pending_activations,
- pending_activation->service_name);
+ _dbus_verbose ("Failed to spawn child\n");
+ _DBUS_ASSERT_ERROR_IS_SET (error);
+ return FALSE;
+ }
+
+ _dbus_assert (pending_activation->babysitter != NULL);
+
+ if (!_dbus_babysitter_set_watch_functions (pending_activation->babysitter,
+ add_babysitter_watch,
+ remove_babysitter_watch,
+ NULL,
+ pending_activation,
+ NULL))
+ {
+ BUS_SET_OOM (error);
+ _dbus_verbose ("Failed to set babysitter watch functions\n");
return FALSE;
}