dbus/bus/services.c
John (J5) Palmieri 1a163e765c * configure.in: Add test/name-test/Makefile to the generated
Makefile list

	* dbus/dbus-shared.h (#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT):
	New flag which replaces DBUS_NAME_FLAG_PROHIBIT_REPLACEMENT
	(#define DBUS_NAME_FLAG_DO_NOT_QUEUE): New flag for specifying
	not to queue an ower if it can't be the primary owner

	* bus/bus.h: Add new internal BusOwner struct

	* bus/driver.c (bus_driver_handle_hello): Send flags (0 for default)
	to bus_registry_ensure and don't set the prohibit_replacement flag
	since they are now set per BusOwner and not per name.
	(bus_driver_handle_list_queued_owners): bus method (ListQueuedOwners)
	that returns the list of connections in a name's connection queue

	* bus/services.c (struct BusService): remove prohibit_replacement field
	(struct BusOwner): new struct for keeping track of queued connections
	and their associated flags for the queue
	(struct BusRegistry): add a BusOwner memory pool
	(bus_registry_new): initialize the BusOwner memory pool
	(bus_registry_unref): free the BusOwner memory pool
	(_bus_service_find_owner_link): new internal method for
	searching the queue for a specific connection
	(bus_owner_set_flags): new method for adding setting the flags on a
	bus owner
	(bus_owner_new): new method that creates a BusOwner object from the
	pool and sets its flags
	(bus_owner_ref, bus_owner_unref): ref counting for BusOwner objects
	(bus_registry_ensure): Add the flags parameter
	(bus_registry_acquire_service): Switch from using raw connections to
	using the BusOwner struct
	Add new state machine for dealing with the new set of flags
	(bus_registry_set_service_context_table, struct OwnershipCancelData,
	cancel_ownership, free_ownership_cancel_data,
	add_cancel_ownership_to_transaction, struct OwnershipRestoreData,
	restore_ownership, free_ownership_restore_data,
	add_restore_ownership_to_transaction): Switch to using BusOwner
	instead of raw connections
	(bus_service_add_owner): Add flags parameter
	Switch to using BusOwner instead of raw connections
	Add state machine for dealing with the new set of flags
	(bus_service_swap_owner): Swaps the first and second owners in the
	queue.  Used to make sure proper signals are sent when a service looses
	or gains primary ownership.  We never insert an owner at the top of the
	queue.  Instead we insert it in the second position and then swap.
	(bus_service_remove_owner): Remove the owner from the queue sending
	out the NameLost and NameOwnerChanged signals if the we were the
	primary owner
	(bus_service_get_primary_owners_connection): New method that extracts
	the connection from the primary owner
	(bus_service_get_primary_owner): Returns the BusOwner instead of the
	connection
	(bus_service_get_allow_replacement): Changed from the old
	bus_service_get_prohibit_replacement method.  Checks the flags of the
	primary owner and returns if it can be replaced or not
	(bus_service_set_prohibit_replacement): removed
	(bus_service_has_owner): returns TRUE if and owner with
	the specified connection exists in the queue

	* dbus/dbus-bus.c (dbus_bus_connection_get_unique_name): New helper
	method that only compiles if tests are enabled.  Allows us to get the
	unique name of a connection so we can check it against the queue when
	doing regression tests

	* bus/activation.c (bus_activation_send_pending_auto_activate),
	bus/dispatch.c (bus_dispatch),
	bus/driver.c (bus_driver_handle_get_service_owner,
	bus_driver_handle_get_connection_unix_user,
	bus_driver_handle_get_connection_unix_process_id,
	bus_driver_handle_get_connection_selinux_security_context),
	bus/signals.c (connection_is_primary_owner):
	use bus_service_get_primary_owners_connection instead of
	bus_service_get_primary_owner

	* dbus/dbus-sysdeps.c (_dbus_connect_unix_socket,
	_dbus_listen_unix_socket): Calculate the length of the socket
	path and use that instead of using a fixed length which was
	causing socket names to contain many trailing Nul bytes.

	* dbus/dbus-glib-lowlevel.h, glib/dbus-gobject.c
	(dbus_g_method_get_sender): New method for extracting the sender
	from a DBusGMethodInvocation
	(dbus_g_method_return_get_reply): changed name to
	dbus_g_method_get_reply
	(dbus_g_method_return_send_reply): changed name to
	dbus_g_method_send reply

	* doc/dbus-specification.xml: New docs that describe how the new
	queueing system works and talks about the changes to the how
	we specify socket names

	* glib/examples/example-service.c,
	glib/examples/example-signal-emitter.c,
	glib/examples/statemachine/statemachine-server.c:
	Changed the RequestName flags to the new system

	* test/name-test/ (test-names.c, run-test.sh, Makefile.am): New
	regression test suite for testing various states of the new
	queueing system
2005-11-22 20:37:00 +00:00

1281 lines
34 KiB
C

/* -*- mode: C; c-file-style: "gnu" -*- */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <dbus/dbus-hash.h>
#include <dbus/dbus-list.h>
#include <dbus/dbus-mempool.h>
#include <dbus/dbus-marshal-validate.h>
#include "driver.h"
#include "services.h"
#include "connection.h"
#include "utils.h"
#include "activation.h"
#include "policy.h"
#include "bus.h"
#include "selinux.h"
struct BusService
{
int refcount;
BusRegistry *registry;
char *name;
DBusList *owners;
};
struct BusOwner
{
int refcount;
BusService *service;
DBusConnection *conn;
unsigned int allow_replacement : 1;
unsigned int do_not_queue : 1;
};
struct BusRegistry
{
int refcount;
BusContext *context;
DBusHashTable *service_hash;
DBusMemPool *service_pool;
DBusMemPool *owner_pool;
DBusHashTable *service_sid_table;
};
BusRegistry*
bus_registry_new (BusContext *context)
{
BusRegistry *registry;
registry = dbus_new0 (BusRegistry, 1);
if (registry == NULL)
return NULL;
registry->refcount = 1;
registry->context = context;
registry->service_hash = _dbus_hash_table_new (DBUS_HASH_STRING,
NULL, NULL);
if (registry->service_hash == NULL)
goto failed;
registry->service_pool = _dbus_mem_pool_new (sizeof (BusService),
TRUE);
if (registry->service_pool == NULL)
goto failed;
registry->owner_pool = _dbus_mem_pool_new (sizeof (BusOwner),
TRUE);
if (registry->owner_pool == NULL)
goto failed;
registry->service_sid_table = NULL;
return registry;
failed:
bus_registry_unref (registry);
return NULL;
}
BusRegistry *
bus_registry_ref (BusRegistry *registry)
{
_dbus_assert (registry->refcount > 0);
registry->refcount += 1;
return registry;
}
void
bus_registry_unref (BusRegistry *registry)
{
_dbus_assert (registry->refcount > 0);
registry->refcount -= 1;
if (registry->refcount == 0)
{
if (registry->service_hash)
_dbus_hash_table_unref (registry->service_hash);
if (registry->service_pool)
_dbus_mem_pool_free (registry->service_pool);
if (registry->owner_pool)
_dbus_mem_pool_free (registry->owner_pool);
if (registry->service_sid_table)
_dbus_hash_table_unref (registry->service_sid_table);
dbus_free (registry);
}
}
BusService*
bus_registry_lookup (BusRegistry *registry,
const DBusString *service_name)
{
BusService *service;
service = _dbus_hash_table_lookup_string (registry->service_hash,
_dbus_string_get_const_data (service_name));
return service;
}
static DBusList *
_bus_service_find_owner_link (BusService *service,
DBusConnection *connection)
{
DBusList *link;
link = _dbus_list_get_first_link (&service->owners);
while (link != NULL)
{
BusOwner *bus_owner;
bus_owner = (BusOwner *) link->data;
if (bus_owner->conn == connection)
break;
link = _dbus_list_get_next_link (&service->owners, link);
}
return link;
}
static void
bus_owner_set_flags (BusOwner *owner,
dbus_uint32_t flags)
{
owner->allow_replacement =
(flags & DBUS_NAME_FLAG_ALLOW_REPLACEMENT) != FALSE;
owner->do_not_queue =
(flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) != FALSE;
}
static BusOwner *
bus_owner_new (BusService *service,
DBusConnection *conn,
dbus_uint32_t flags)
{
BusOwner *result;
result = _dbus_mem_pool_alloc (service->registry->owner_pool);
if (result != NULL)
{
result->refcount = 1;
/* don't ref the connection because we don't want
to block the connection from going away.
transactions take care of reffing the connection
but we need to use refcounting on the owner
so that the owner does not get freed before
we can deref the connection in the transaction
*/
result->conn = conn;
result->service = service;
if (!bus_connection_add_owned_service (conn, service))
{
_dbus_mem_pool_dealloc (service->registry->owner_pool, result);
return NULL;
}
bus_owner_set_flags (result, flags);
}
return result;
}
static BusOwner *
bus_owner_ref (BusOwner *owner)
{
_dbus_assert (owner->refcount > 0);
owner->refcount += 1;
return owner;
}
static void
bus_owner_unref (BusOwner *owner)
{
_dbus_assert (owner->refcount > 0);
owner->refcount -= 1;
if (owner->refcount == 0)
{
bus_connection_remove_owned_service (owner->conn, owner->service);
_dbus_mem_pool_dealloc (owner->service->registry->owner_pool, owner);
}
}
BusService*
bus_registry_ensure (BusRegistry *registry,
const DBusString *service_name,
DBusConnection *owner_connection_if_created,
dbus_uint32_t flags,
BusTransaction *transaction,
DBusError *error)
{
BusService *service;
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
_dbus_assert (owner_connection_if_created != NULL);
_dbus_assert (transaction != NULL);
service = _dbus_hash_table_lookup_string (registry->service_hash,
_dbus_string_get_const_data (service_name));
if (service != NULL)
return service;
service = _dbus_mem_pool_alloc (registry->service_pool);
if (service == NULL)
{
BUS_SET_OOM (error);
return NULL;
}
service->registry = registry;
service->refcount = 1;
_dbus_verbose ("copying string %p '%s' to service->name\n",
service_name, _dbus_string_get_const_data (service_name));
if (!_dbus_string_copy_data (service_name, &service->name))
{
_dbus_mem_pool_dealloc (registry->service_pool, service);
BUS_SET_OOM (error);
return NULL;
}
_dbus_verbose ("copied string %p '%s' to '%s'\n",
service_name, _dbus_string_get_const_data (service_name),
service->name);
if (!bus_driver_send_service_owner_changed (service->name,
NULL,
bus_connection_get_name (owner_connection_if_created),
transaction, error))
{
bus_service_unref (service);
return NULL;
}
if (!bus_activation_service_created (bus_context_get_activation (registry->context),
service->name, transaction, error))
{
bus_service_unref (service);
return NULL;
}
if (!bus_service_add_owner (service, owner_connection_if_created, flags,
transaction, error))
{
bus_service_unref (service);
return NULL;
}
if (!_dbus_hash_table_insert_string (registry->service_hash,
service->name,
service))
{
/* The add_owner gets reverted on transaction cancel */
BUS_SET_OOM (error);
return NULL;
}
return service;
}
void
bus_registry_foreach (BusRegistry *registry,
BusServiceForeachFunction function,
void *data)
{
DBusHashIter iter;
_dbus_hash_iter_init (registry->service_hash, &iter);
while (_dbus_hash_iter_next (&iter))
{
BusService *service = _dbus_hash_iter_get_value (&iter);
(* function) (service, data);
}
}
dbus_bool_t
bus_registry_list_services (BusRegistry *registry,
char ***listp,
int *array_len)
{
int i, j, len;
char **retval;
DBusHashIter iter;
len = _dbus_hash_table_get_n_entries (registry->service_hash);
retval = dbus_new (char *, len + 1);
if (retval == NULL)
return FALSE;
_dbus_hash_iter_init (registry->service_hash, &iter);
i = 0;
while (_dbus_hash_iter_next (&iter))
{
BusService *service = _dbus_hash_iter_get_value (&iter);
retval[i] = _dbus_strdup (service->name);
if (retval[i] == NULL)
goto error;
i++;
}
retval[i] = NULL;
if (array_len)
*array_len = len;
*listp = retval;
return TRUE;
error:
for (j = 0; j < i; j++)
dbus_free (retval[i]);
dbus_free (retval);
return FALSE;
}
dbus_bool_t
bus_registry_acquire_service (BusRegistry *registry,
DBusConnection *connection,
const DBusString *service_name,
dbus_uint32_t flags,
dbus_uint32_t *result,
BusTransaction *transaction,
DBusError *error)
{
dbus_bool_t retval;
DBusConnection *old_owner_conn;
DBusConnection *current_owner_conn;
BusClientPolicy *policy;
BusService *service;
BusActivation *activation;
BusSELinuxID *sid;
BusOwner *primary_owner;
retval = FALSE;
if (!_dbus_validate_bus_name (service_name, 0,
_dbus_string_get_length (service_name)))
{
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"Requested bus name \"%s\" is not valid",
_dbus_string_get_const_data (service_name));
_dbus_verbose ("Attempt to acquire invalid service name\n");
goto out;
}
if (_dbus_string_get_byte (service_name, 0) == ':')
{
/* Not allowed; only base services can start with ':' */
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"Cannot acquire a service starting with ':' such as \"%s\"",
_dbus_string_get_const_data (service_name));
_dbus_verbose ("Attempt to acquire invalid base service name \"%s\"",
_dbus_string_get_const_data (service_name));
goto out;
}
policy = bus_connection_get_policy (connection);
_dbus_assert (policy != NULL);
/* Note that if sid is #NULL then the bus's own context gets used
* in bus_connection_selinux_allows_acquire_service()
*/
sid = bus_selinux_id_table_lookup (registry->service_sid_table,
service_name);
if (!bus_selinux_allows_acquire_service (connection, sid,
_dbus_string_get_const_data (service_name), error))
{
if (dbus_error_is_set (error) &&
dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY))
{
goto out;
}
dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
"Connection \"%s\" is not allowed to own the service \"%s\" due "
"to SELinux policy",
bus_connection_is_active (connection) ?
bus_connection_get_name (connection) :
"(inactive)",
_dbus_string_get_const_data (service_name));
goto out;
}
if (!bus_client_policy_check_can_own (policy, connection,
service_name))
{
dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
"Connection \"%s\" is not allowed to own the service \"%s\" due "
"to security policies in the configuration file",
bus_connection_is_active (connection) ?
bus_connection_get_name (connection) :
"(inactive)",
_dbus_string_get_const_data (service_name));
goto out;
}
if (bus_connection_get_n_services_owned (connection) >=
bus_context_get_max_services_per_connection (registry->context))
{
dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
"Connection \"%s\" is not allowed to own more services "
"(increase limits in configuration file if required)",
bus_connection_is_active (connection) ?
bus_connection_get_name (connection) :
"(inactive)");
goto out;
}
service = bus_registry_lookup (registry, service_name);
if (service != NULL)
{
primary_owner = bus_service_get_primary_owner (service);
if (primary_owner != NULL)
old_owner_conn = primary_owner->conn;
else
old_owner_conn = NULL;
}
else
old_owner_conn = NULL;
if (service == NULL)
{
service = bus_registry_ensure (registry,
service_name, connection, flags,
transaction, error);
if (service == NULL)
goto out;
}
primary_owner = bus_service_get_primary_owner (service);
if (primary_owner == NULL)
goto out;
current_owner_conn = primary_owner->conn;
if (old_owner_conn == NULL)
{
_dbus_assert (current_owner_conn == connection);
*result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
}
else if (old_owner_conn == connection)
{
bus_owner_set_flags (primary_owner, flags);
*result = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER;
}
else if (((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) &&
!(bus_service_get_allow_replacement (service))) ||
((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) &&
!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING)))
{
DBusList *link;
BusOwner *temp_owner;
/* Since we can't be queued if we are already in the queue
remove us */
link = _bus_service_find_owner_link (service, connection);
if (link != NULL)
{
_dbus_list_unlink (&service->owners, link);
temp_owner = (BusOwner *)link->data;
bus_owner_unref (temp_owner);
_dbus_list_free_link (link);
}
*result = DBUS_REQUEST_NAME_REPLY_EXISTS;
}
else if (!(flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) &&
(!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) ||
!(bus_service_get_allow_replacement (service))))
{
/* Queue the connection */
if (!bus_service_add_owner (service, connection,
flags,
transaction, error))
goto out;
*result = DBUS_REQUEST_NAME_REPLY_IN_QUEUE;
}
else
{
/* Replace the current owner */
/* We enqueue the new owner and remove the first one because
* that will cause NameAcquired and NameLost messages to
* be sent.
*/
if (!bus_service_add_owner (service, connection,
flags,
transaction, error))
goto out;
if (primary_owner->do_not_queue)
{
if (!bus_service_remove_owner (service, old_owner_conn,
transaction, error))
goto out;
}
else
{
if (!bus_service_swap_owner (service, old_owner_conn,
transaction, error))
goto out;
}
_dbus_assert (connection == bus_service_get_primary_owner (service)->conn);
*result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
}
activation = bus_context_get_activation (registry->context);
retval = bus_activation_send_pending_auto_activation_messages (activation,
service,
transaction,
error);
out:
return retval;
}
dbus_bool_t
bus_registry_release_service (BusRegistry *registry,
DBusConnection *connection,
const DBusString *service_name,
dbus_uint32_t *result,
BusTransaction *transaction,
DBusError *error)
{
dbus_bool_t retval;
BusService *service;
retval = FALSE;
if (!_dbus_validate_bus_name (service_name, 0,
_dbus_string_get_length (service_name)))
{
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"Given bus name \"%s\" is not valid",
_dbus_string_get_const_data (service_name));
_dbus_verbose ("Attempt to release invalid service name\n");
goto out;
}
if (_dbus_string_get_byte (service_name, 0) == ':')
{
/* Not allowed; the base service name cannot be created or released */
dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
"Cannot release a service starting with ':' such as \"%s\"",
_dbus_string_get_const_data (service_name));
_dbus_verbose ("Attempt to release invalid base service name \"%s\"",
_dbus_string_get_const_data (service_name));
goto out;
}
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, 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;
}