dbus/bus/connection.c
Havoc Pennington a37404111b Make BusExpireList an opaque data type
2007-11-08  Havoc Pennington  <hp@redhat.com>

	* bus/connection.c, bus/expirelist.c: Make the BusExpireList
	struct opaque, adding accessors for manipulating the list. In this
	commit there should be no change in functionality or behavior. The
	purpose of this change is to improve encapsulation prior to fixing
	some bugs Kimmo Hämäläinen found where the timeout is not properly
	updated, since we need to e.g. take some action whenever adding
	and removing stuff from the expire list.
2008-03-04 14:21:42 -05:00

2219 lines
62 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* connection.c Client connections
*
* Copyright (C) 2003 Red Hat, Inc.
*
* 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 "connection.h"
#include "dispatch.h"
#include "policy.h"
#include "services.h"
#include "utils.h"
#include "signals.h"
#include "expirelist.h"
#include "selinux.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-hash.h>
#include <dbus/dbus-timeout.h>
static void bus_connection_remove_transactions (DBusConnection *connection);
typedef struct
{
BusExpireItem expire_item;
DBusConnection *will_get_reply;
DBusConnection *will_send_reply;
dbus_uint32_t reply_serial;
} BusPendingReply;
struct BusConnections
{
int refcount;
DBusList *completed; /**< List of all completed connections */
int n_completed; /**< Length of completed list */
DBusList *incomplete; /**< List of all not-yet-active connections */
int n_incomplete; /**< Length of incomplete list */
BusContext *context;
DBusHashTable *completed_by_user; /**< Number of completed connections for each UID */
DBusTimeout *expire_timeout; /**< Timeout for expiring incomplete connections. */
int stamp; /**< Incrementing number */
BusExpireList *pending_replies; /**< List of pending replies */
};
static dbus_int32_t connection_data_slot = -1;
typedef struct
{
BusConnections *connections;
DBusList *link_in_connection_list;
DBusConnection *connection;
DBusList *services_owned;
int n_services_owned;
DBusList *match_rules;
int n_match_rules;
char *name;
DBusList *transaction_messages; /**< Stuff we need to send as part of a transaction */
DBusMessage *oom_message;
DBusPreallocatedSend *oom_preallocated;
BusClientPolicy *policy;
BusSELinuxID *selinux_id;
long connection_tv_sec; /**< Time when we connected (seconds component) */
long connection_tv_usec; /**< Time when we connected (microsec component) */
int stamp; /**< connections->stamp last time we were traversed */
} BusConnectionData;
static dbus_bool_t bus_pending_reply_expired (BusExpireList *list,
DBusList *link,
void *data);
static void bus_connection_drop_pending_replies (BusConnections *connections,
DBusConnection *connection);
static dbus_bool_t expire_incomplete_timeout (void *data);
#define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot))
static DBusLoop*
connection_get_loop (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
return bus_context_get_loop (d->connections->context);
}
static int
get_connections_for_uid (BusConnections *connections,
dbus_uid_t uid)
{
void *val;
int current_count;
/* val is NULL is 0 when it isn't in the hash yet */
val = _dbus_hash_table_lookup_ulong (connections->completed_by_user,
uid);
current_count = _DBUS_POINTER_TO_INT (val);
return current_count;
}
static dbus_bool_t
adjust_connections_for_uid (BusConnections *connections,
dbus_uid_t uid,
int adjustment)
{
int current_count;
current_count = get_connections_for_uid (connections, uid);
_dbus_verbose ("Adjusting connection count for UID " DBUS_UID_FORMAT
": was %d adjustment %d making %d\n",
uid, current_count, adjustment, current_count + adjustment);
_dbus_assert (current_count >= 0);
current_count += adjustment;
_dbus_assert (current_count >= 0);
if (current_count == 0)
{
_dbus_hash_table_remove_ulong (connections->completed_by_user, uid);
return TRUE;
}
else
{
dbus_bool_t retval;
retval = _dbus_hash_table_insert_ulong (connections->completed_by_user,
uid, _DBUS_INT_TO_POINTER (current_count));
/* only positive adjustment can fail as otherwise
* a hash entry should already exist
*/
_dbus_assert (adjustment > 0 ||
(adjustment <= 0 && retval));
return retval;
}
}
void
bus_connection_disconnected (DBusConnection *connection)
{
BusConnectionData *d;
BusService *service;
BusMatchmaker *matchmaker;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_verbose ("%s disconnected, dropping all service ownership and releasing\n",
d->name ? d->name : "(inactive)");
/* Delete our match rules */
if (d->n_match_rules > 0)
{
matchmaker = bus_context_get_matchmaker (d->connections->context);
bus_matchmaker_disconnected (matchmaker, connection);
}
/* Drop any service ownership. Unfortunately, this requires
* memory allocation and there doesn't seem to be a good way to
* handle it other than sleeping; we can't "fail" the operation of
* disconnecting a client, and preallocating a broadcast "service is
* now gone" message for every client-service pair seems kind of
* involved.
*/
while ((service = _dbus_list_get_last (&d->services_owned)))
{
BusTransaction *transaction;
DBusError error;
retry:
dbus_error_init (&error);
while ((transaction = bus_transaction_new (d->connections->context)) == NULL)
_dbus_wait_for_memory ();
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);
bus_transaction_cancel_and_free (transaction);
_dbus_wait_for_memory ();
goto retry;
}
else
{
_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);
}
bus_dispatch_remove_connection (connection);
/* no more watching */
if (!dbus_connection_set_watch_functions (connection,
NULL, NULL, NULL,
connection,
NULL))
_dbus_assert_not_reached ("setting watch functions to NULL failed");
if (!dbus_connection_set_timeout_functions (connection,
NULL, NULL, NULL,
connection,
NULL))
_dbus_assert_not_reached ("setting timeout functions to NULL failed");
dbus_connection_set_unix_user_function (connection,
NULL, NULL, NULL);
dbus_connection_set_windows_user_function (connection,
NULL, NULL, NULL);
dbus_connection_set_dispatch_status_function (connection,
NULL, NULL, NULL);
bus_connection_remove_transactions (connection);
if (d->link_in_connection_list != NULL)
{
if (d->name != NULL)
{
unsigned long uid;
_dbus_list_remove_link (&d->connections->completed, d->link_in_connection_list);
d->link_in_connection_list = NULL;
d->connections->n_completed -= 1;
if (dbus_connection_get_unix_user (connection, &uid))
{
if (!adjust_connections_for_uid (d->connections,
uid, -1))
_dbus_assert_not_reached ("adjusting downward should never fail");
}
}
else
{
_dbus_list_remove_link (&d->connections->incomplete, d->link_in_connection_list);
d->link_in_connection_list = NULL;
d->connections->n_incomplete -= 1;
}
_dbus_assert (d->connections->n_incomplete >= 0);
_dbus_assert (d->connections->n_completed >= 0);
}
bus_connection_drop_pending_replies (d->connections, connection);
/* frees "d" as side effect */
dbus_connection_set_data (connection,
connection_data_slot,
NULL, NULL);
dbus_connection_unref (connection);
}
static dbus_bool_t
connection_watch_callback (DBusWatch *watch,
unsigned int condition,
void *data)
{
/* FIXME this can be done in dbus-mainloop.c
* if the code in activation.c for the babysitter
* watch handler is fixed.
*/
#if 0
_dbus_verbose ("Calling handle_watch\n");
#endif
return dbus_watch_handle (watch, condition);
}
static dbus_bool_t
add_connection_watch (DBusWatch *watch,
void *data)
{
DBusConnection *connection = data;
return _dbus_loop_add_watch (connection_get_loop (connection),
watch, connection_watch_callback, connection,
NULL);
}
static void
remove_connection_watch (DBusWatch *watch,
void *data)
{
DBusConnection *connection = data;
_dbus_loop_remove_watch (connection_get_loop (connection),
watch, connection_watch_callback, connection);
}
static void
connection_timeout_callback (DBusTimeout *timeout,
void *data)
{
/* DBusConnection *connection = data; */
/* can return FALSE on OOM but we just let it fire again later */
dbus_timeout_handle (timeout);
}
static dbus_bool_t
add_connection_timeout (DBusTimeout *timeout,
void *data)
{
DBusConnection *connection = data;
return _dbus_loop_add_timeout (connection_get_loop (connection),
timeout, connection_timeout_callback, connection, NULL);
}
static void
remove_connection_timeout (DBusTimeout *timeout,
void *data)
{
DBusConnection *connection = data;
_dbus_loop_remove_timeout (connection_get_loop (connection),
timeout, connection_timeout_callback, connection);
}
static void
dispatch_status_function (DBusConnection *connection,
DBusDispatchStatus new_status,
void *data)
{
DBusLoop *loop = data;
if (new_status != DBUS_DISPATCH_COMPLETE)
{
while (!_dbus_loop_queue_dispatch (loop, connection))
_dbus_wait_for_memory ();
}
}
static dbus_bool_t
allow_unix_user_function (DBusConnection *connection,
unsigned long uid,
void *data)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return bus_context_allow_unix_user (d->connections->context, uid);
}
static void
free_connection_data (void *data)
{
BusConnectionData *d = data;
/* services_owned should be NULL since we should be disconnected */
_dbus_assert (d->services_owned == NULL);
_dbus_assert (d->n_services_owned == 0);
/* similarly */
_dbus_assert (d->transaction_messages == NULL);
if (d->oom_preallocated)
dbus_connection_free_preallocated_send (d->connection, d->oom_preallocated);
if (d->oom_message)
dbus_message_unref (d->oom_message);
if (d->policy)
bus_client_policy_unref (d->policy);
if (d->selinux_id)
bus_selinux_id_unref (d->selinux_id);
dbus_free (d->name);
dbus_free (d);
}
static void
call_timeout_callback (DBusTimeout *timeout,
void *data)
{
/* can return FALSE on OOM but we just let it fire again later */
dbus_timeout_handle (timeout);
}
BusConnections*
bus_connections_new (BusContext *context)
{
BusConnections *connections;
if (!dbus_connection_allocate_data_slot (&connection_data_slot))
goto failed_0;
connections = dbus_new0 (BusConnections, 1);
if (connections == NULL)
goto failed_1;
connections->completed_by_user = _dbus_hash_table_new (DBUS_HASH_ULONG,
NULL, NULL);
if (connections->completed_by_user == NULL)
goto failed_2;
connections->expire_timeout = _dbus_timeout_new (100, /* irrelevant */
expire_incomplete_timeout,
connections, NULL);
if (connections->expire_timeout == NULL)
goto failed_3;
_dbus_timeout_set_enabled (connections->expire_timeout, FALSE);
connections->pending_replies = bus_expire_list_new (bus_context_get_loop (context),
bus_context_get_reply_timeout (context),
bus_pending_reply_expired,
connections);
if (connections->pending_replies == NULL)
goto failed_4;
if (!_dbus_loop_add_timeout (bus_context_get_loop (context),
connections->expire_timeout,
call_timeout_callback, NULL, NULL))
goto failed_5;
connections->refcount = 1;
connections->context = context;
return connections;
failed_5:
bus_expire_list_free (connections->pending_replies);
failed_4:
_dbus_timeout_unref (connections->expire_timeout);
failed_3:
_dbus_hash_table_unref (connections->completed_by_user);
failed_2:
dbus_free (connections);
failed_1:
dbus_connection_free_data_slot (&connection_data_slot);
failed_0:
return NULL;
}
BusConnections *
bus_connections_ref (BusConnections *connections)
{
_dbus_assert (connections->refcount > 0);
connections->refcount += 1;
return connections;
}
void
bus_connections_unref (BusConnections *connections)
{
_dbus_assert (connections->refcount > 0);
connections->refcount -= 1;
if (connections->refcount == 0)
{
/* drop all incomplete */
while (connections->incomplete != NULL)
{
DBusConnection *connection;
connection = connections->incomplete->data;
dbus_connection_ref (connection);
dbus_connection_close (connection);
bus_connection_disconnected (connection);
dbus_connection_unref (connection);
}
_dbus_assert (connections->n_incomplete == 0);
/* drop all real connections */
while (connections->completed != NULL)
{
DBusConnection *connection;
connection = connections->completed->data;
dbus_connection_ref (connection);
dbus_connection_close (connection);
bus_connection_disconnected (connection);
dbus_connection_unref (connection);
}
_dbus_assert (connections->n_completed == 0);
bus_expire_list_free (connections->pending_replies);
_dbus_loop_remove_timeout (bus_context_get_loop (connections->context),
connections->expire_timeout,
call_timeout_callback, NULL);
_dbus_timeout_unref (connections->expire_timeout);
_dbus_hash_table_unref (connections->completed_by_user);
dbus_free (connections);
dbus_connection_free_data_slot (&connection_data_slot);
}
}
dbus_bool_t
bus_connections_setup_connection (BusConnections *connections,
DBusConnection *connection)
{
BusConnectionData *d;
dbus_bool_t retval;
DBusError error;
d = dbus_new0 (BusConnectionData, 1);
if (d == NULL)
return FALSE;
d->connections = connections;
d->connection = connection;
_dbus_get_current_time (&d->connection_tv_sec,
&d->connection_tv_usec);
_dbus_assert (connection_data_slot >= 0);
if (!dbus_connection_set_data (connection,
connection_data_slot,
d, free_connection_data))
{
dbus_free (d);
return FALSE;
}
dbus_connection_set_route_peer_messages (connection, TRUE);
retval = FALSE;
dbus_error_init (&error);
d->selinux_id = bus_selinux_init_connection_id (connection,
&error);
if (dbus_error_is_set (&error))
{
/* This is a bit bogus because we pretend all errors
* are OOM; this is done because we know that in bus.c
* an OOM error disconnects the connection, which is
* the same thing we want on any other error.
*/
dbus_error_free (&error);
goto out;
}
if (!dbus_connection_set_watch_functions (connection,
add_connection_watch,
remove_connection_watch,
NULL,
connection,
NULL))
goto out;
if (!dbus_connection_set_timeout_functions (connection,
add_connection_timeout,
remove_connection_timeout,
NULL,
connection, NULL))
goto out;
/* For now we don't need to set a Windows user function because
* there are no policies in the config file controlling what
* Windows users can connect. The default 'same user that owns the
* bus can connect' behavior of DBusConnection is fine on Windows.
*/
dbus_connection_set_unix_user_function (connection,
allow_unix_user_function,
NULL, NULL);
dbus_connection_set_dispatch_status_function (connection,
dispatch_status_function,
bus_context_get_loop (connections->context),
NULL);
d->link_in_connection_list = _dbus_list_alloc_link (connection);
if (d->link_in_connection_list == NULL)
goto out;
/* Setup the connection with the dispatcher */
if (!bus_dispatch_add_connection (connection))
goto out;
if (dbus_connection_get_dispatch_status (connection) != DBUS_DISPATCH_COMPLETE)
{
if (!_dbus_loop_queue_dispatch (bus_context_get_loop (connections->context), connection))
{
bus_dispatch_remove_connection (connection);
goto out;
}
}
_dbus_list_append_link (&connections->incomplete, d->link_in_connection_list);
connections->n_incomplete += 1;
dbus_connection_ref (connection);
/* Note that we might disconnect ourselves here, but it only takes
* effect on return to the main loop. We call this to free up
* expired connections if possible, and to queue the timeout for our
* own expiration.
*/
bus_connections_expire_incomplete (connections);
/* And we might also disconnect ourselves here, but again it
* only takes effect on return to main loop.
*/
if (connections->n_incomplete >
bus_context_get_max_incomplete_connections (connections->context))
{
_dbus_verbose ("Number of incomplete connections exceeds max, dropping oldest one\n");
_dbus_assert (connections->incomplete != NULL);
/* Disconnect the oldest unauthenticated connection. FIXME
* would it be more secure to drop a *random* connection? This
* algorithm seems to mean that if someone can create new
* connections quickly enough, they can keep anyone else from
* completing authentication. But random may or may not really
* help with that, a more elaborate solution might be required.
*/
dbus_connection_close (connections->incomplete->data);
}
retval = TRUE;
out:
if (!retval)
{
if (d->selinux_id)
bus_selinux_id_unref (d->selinux_id);
d->selinux_id = NULL;
if (!dbus_connection_set_watch_functions (connection,
NULL, NULL, NULL,
connection,
NULL))
_dbus_assert_not_reached ("setting watch functions to NULL failed");
if (!dbus_connection_set_timeout_functions (connection,
NULL, NULL, NULL,
connection,
NULL))
_dbus_assert_not_reached ("setting timeout functions to NULL failed");
dbus_connection_set_unix_user_function (connection,
NULL, NULL, NULL);
dbus_connection_set_windows_user_function (connection,
NULL, NULL, NULL);
dbus_connection_set_dispatch_status_function (connection,
NULL, NULL, NULL);
if (d->link_in_connection_list != NULL)
{
_dbus_assert (d->link_in_connection_list->next == NULL);
_dbus_assert (d->link_in_connection_list->prev == NULL);
_dbus_list_free_link (d->link_in_connection_list);
d->link_in_connection_list = NULL;
}
if (!dbus_connection_set_data (connection,
connection_data_slot,
NULL, NULL))
_dbus_assert_not_reached ("failed to set connection data to null");
/* "d" has now been freed */
}
return retval;
}
void
bus_connections_expire_incomplete (BusConnections *connections)
{
int next_interval;
next_interval = -1;
if (connections->incomplete != NULL)
{
long tv_sec, tv_usec;
DBusList *link;
int auth_timeout;
_dbus_get_current_time (&tv_sec, &tv_usec);
auth_timeout = bus_context_get_auth_timeout (connections->context);
link = _dbus_list_get_first_link (&connections->incomplete);
while (link != NULL)
{
DBusList *next = _dbus_list_get_next_link (&connections->incomplete, link);
DBusConnection *connection;
BusConnectionData *d;
double elapsed;
connection = link->data;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
elapsed = ELAPSED_MILLISECONDS_SINCE (d->connection_tv_sec,
d->connection_tv_usec,
tv_sec, tv_usec);
if (elapsed >= (double) auth_timeout)
{
_dbus_verbose ("Timing out authentication for connection %p\n", connection);
dbus_connection_close (connection);
}
else
{
/* We can end the loop, since the connections are in oldest-first order */
next_interval = ((double)auth_timeout) - elapsed;
_dbus_verbose ("Connection %p authentication expires in %d milliseconds\n",
connection, next_interval);
break;
}
link = next;
}
}
bus_expire_timeout_set_interval (connections->expire_timeout,
next_interval);
}
static dbus_bool_t
expire_incomplete_timeout (void *data)
{
BusConnections *connections = data;
_dbus_verbose ("Running %s\n", _DBUS_FUNCTION_NAME);
/* note that this may remove the timeout */
bus_connections_expire_incomplete (connections);
return TRUE;
}
dbus_bool_t
bus_connection_get_unix_groups (DBusConnection *connection,
unsigned long **groups,
int *n_groups,
DBusError *error)
{
BusConnectionData *d;
unsigned long uid;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
*groups = NULL;
*n_groups = 0;
if (dbus_connection_get_unix_user (connection, &uid))
{
if (!_dbus_unix_groups_from_uid (uid, groups, n_groups))
{
_dbus_verbose ("Did not get any groups for UID %lu\n",
uid);
return FALSE;
}
else
{
_dbus_verbose ("Got %d groups for UID %lu\n",
*n_groups, uid);
return TRUE;
}
}
else
return TRUE; /* successfully got 0 groups */
}
dbus_bool_t
bus_connection_is_in_unix_group (DBusConnection *connection,
unsigned long gid)
{
int i;
unsigned long *group_ids;
int n_group_ids;
if (!bus_connection_get_unix_groups (connection, &group_ids, &n_group_ids,
NULL))
return FALSE;
i = 0;
while (i < n_group_ids)
{
if (group_ids[i] == gid)
{
dbus_free (group_ids);
return TRUE;
}
++i;
}
dbus_free (group_ids);
return FALSE;
}
BusClientPolicy*
bus_connection_get_policy (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_assert (d->policy != NULL);
return d->policy;
}
static dbus_bool_t
foreach_active (BusConnections *connections,
BusConnectionForeachFunction function,
void *data)
{
DBusList *link;
link = _dbus_list_get_first_link (&connections->completed);
while (link != NULL)
{
DBusConnection *connection = link->data;
DBusList *next = _dbus_list_get_next_link (&connections->completed, link);
if (!(* function) (connection, data))
return FALSE;
link = next;
}
return TRUE;
}
static dbus_bool_t
foreach_inactive (BusConnections *connections,
BusConnectionForeachFunction function,
void *data)
{
DBusList *link;
link = _dbus_list_get_first_link (&connections->incomplete);
while (link != NULL)
{
DBusConnection *connection = link->data;
DBusList *next = _dbus_list_get_next_link (&connections->incomplete, link);
if (!(* function) (connection, data))
return FALSE;
link = next;
}
return TRUE;
}
/**
* Calls function on each active connection; if the function returns
* #FALSE, stops iterating. Active connections are authenticated
* and have sent a Hello message.
*
* @param connections the connections object
* @param function the function
* @param data data to pass to it as a second arg
*/
void
bus_connections_foreach_active (BusConnections *connections,
BusConnectionForeachFunction function,
void *data)
{
foreach_active (connections, function, data);
}
/**
* Calls function on each connection; if the function returns
* #FALSE, stops iterating.
*
* @param connections the connections object
* @param function the function
* @param data data to pass to it as a second arg
*/
void
bus_connections_foreach (BusConnections *connections,
BusConnectionForeachFunction function,
void *data)
{
if (!foreach_active (connections, function, data))
return;
foreach_inactive (connections, function, data);
}
BusContext*
bus_connections_get_context (BusConnections *connections)
{
return connections->context;
}
/*
* This is used to avoid covering the same connection twice when
* traversing connections. Note that it assumes we will
* bus_connection_mark_stamp() each connection at least once per
* INT_MAX increments of the global stamp, or wraparound would break
* things.
*/
void
bus_connections_increment_stamp (BusConnections *connections)
{
connections->stamp += 1;
}
/* Mark connection with current stamp, return TRUE if it
* didn't already have that stamp
*/
dbus_bool_t
bus_connection_mark_stamp (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
if (d->stamp == d->connections->stamp)
return FALSE;
else
{
d->stamp = d->connections->stamp;
return TRUE;
}
}
BusContext*
bus_connection_get_context (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->connections->context;
}
BusConnections*
bus_connection_get_connections (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->connections;
}
BusRegistry*
bus_connection_get_registry (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return bus_context_get_registry (d->connections->context);
}
BusActivation*
bus_connection_get_activation (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return bus_context_get_activation (d->connections->context);
}
BusMatchmaker*
bus_connection_get_matchmaker (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return bus_context_get_matchmaker (d->connections->context);
}
BusSELinuxID*
bus_connection_get_selinux_id (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->selinux_id;
}
/**
* Checks whether the connection is registered with the message bus.
*
* @param connection the connection
* @returns #TRUE if we're an active message bus participant
*/
dbus_bool_t
bus_connection_is_active (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
return d != NULL && d->name != NULL;
}
dbus_bool_t
bus_connection_preallocate_oom_error (DBusConnection *connection)
{
DBusMessage *message;
DBusPreallocatedSend *preallocated;
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
if (d->oom_preallocated != NULL)
return TRUE;
preallocated = dbus_connection_preallocate_send (connection);
if (preallocated == NULL)
return FALSE;
message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR);
if (message == NULL)
{
dbus_connection_free_preallocated_send (connection, preallocated);
return FALSE;
}
/* d->name may be NULL, but that is OK */
if (!dbus_message_set_error_name (message, DBUS_ERROR_NO_MEMORY) ||
!dbus_message_set_destination (message, d->name) ||
!dbus_message_set_sender (message,
DBUS_SERVICE_DBUS))
{
dbus_connection_free_preallocated_send (connection, preallocated);
dbus_message_unref (message);
return FALSE;
}
/* set reply serial to placeholder value just so space is already allocated
* for it.
*/
if (!dbus_message_set_reply_serial (message, 14))
{
dbus_connection_free_preallocated_send (connection, preallocated);
dbus_message_unref (message);
return FALSE;
}
d->oom_message = message;
d->oom_preallocated = preallocated;
return TRUE;
}
void
bus_connection_send_oom_error (DBusConnection *connection,
DBusMessage *in_reply_to)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_assert (d->oom_message != NULL);
/* should always succeed since we set it to a placeholder earlier */
if (!dbus_message_set_reply_serial (d->oom_message,
dbus_message_get_serial (in_reply_to)))
_dbus_assert_not_reached ("Failed to set reply serial for preallocated oom message");
_dbus_assert (dbus_message_get_sender (d->oom_message) != NULL);
dbus_connection_send_preallocated (connection, d->oom_preallocated,
d->oom_message, NULL);
dbus_message_unref (d->oom_message);
d->oom_message = NULL;
d->oom_preallocated = NULL;
}
void
bus_connection_add_match_rule_link (DBusConnection *connection,
DBusList *link)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_list_append_link (&d->match_rules, link);
d->n_match_rules += 1;
}
dbus_bool_t
bus_connection_add_match_rule (DBusConnection *connection,
BusMatchRule *rule)
{
DBusList *link;
link = _dbus_list_alloc_link (rule);
if (link == NULL)
return FALSE;
bus_connection_add_match_rule_link (connection, link);
return TRUE;
}
void
bus_connection_remove_match_rule (DBusConnection *connection,
BusMatchRule *rule)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_list_remove_last (&d->match_rules, rule);
d->n_match_rules -= 1;
_dbus_assert (d->n_match_rules >= 0);
}
int
bus_connection_get_n_match_rules (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->n_match_rules;
}
void
bus_connection_add_owned_service_link (DBusConnection *connection,
DBusList *link)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_list_append_link (&d->services_owned, link);
d->n_services_owned += 1;
}
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;
}
void
bus_connection_remove_owned_service (DBusConnection *connection,
BusService *service)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_list_remove_last (&d->services_owned, service);
d->n_services_owned -= 1;
_dbus_assert (d->n_services_owned >= 0);
}
int
bus_connection_get_n_services_owned (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->n_services_owned;
}
dbus_bool_t
bus_connection_complete (DBusConnection *connection,
const DBusString *name,
DBusError *error)
{
BusConnectionData *d;
unsigned long uid;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
_dbus_assert (d->name == NULL);
_dbus_assert (d->policy == NULL);
_dbus_assert (!bus_connection_is_active (connection));
if (!_dbus_string_copy_data (name, &d->name))
{
BUS_SET_OOM (error);
return FALSE;
}
_dbus_assert (d->name != NULL);
_dbus_verbose ("Name %s assigned to %p\n", d->name, connection);
d->policy = bus_context_create_client_policy (d->connections->context,
connection,
error);
/* we may have a NULL policy on OOM or error getting list of
* groups for a user. In the latter case we don't handle it so
* well currently, as it will just keep failing over and over.
*/
if (d->policy == NULL)
{
_dbus_verbose ("Failed to create security policy for connection %p\n",
connection);
_DBUS_ASSERT_ERROR_IS_SET (error);
dbus_free (d->name);
d->name = NULL;
return FALSE;
}
if (dbus_connection_get_unix_user (connection, &uid))
{
if (!adjust_connections_for_uid (d->connections,
uid, 1))
{
BUS_SET_OOM (error);
dbus_free (d->name);
d->name = NULL;
bus_client_policy_unref (d->policy);
d->policy = NULL;
return FALSE;
}
}
/* Now the connection is active, move it between lists */
_dbus_list_unlink (&d->connections->incomplete,
d->link_in_connection_list);
d->connections->n_incomplete -= 1;
_dbus_list_append_link (&d->connections->completed,
d->link_in_connection_list);
d->connections->n_completed += 1;
_dbus_assert (d->connections->n_incomplete >= 0);
_dbus_assert (d->connections->n_completed > 0);
/* See if we can remove the timeout */
bus_connections_expire_incomplete (d->connections);
_dbus_assert (bus_connection_is_active (connection));
return TRUE;
}
const char *
bus_connection_get_name (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return d->name;
}
/**
* Check whether completing the passed-in connection would
* exceed limits, and if so set error and return #FALSE
*/
dbus_bool_t
bus_connections_check_limits (BusConnections *connections,
DBusConnection *requesting_completion,
DBusError *error)
{
BusConnectionData *d;
unsigned long uid;
d = BUS_CONNECTION_DATA (requesting_completion);
_dbus_assert (d != NULL);
_dbus_assert (d->name == NULL);
if (connections->n_completed >=
bus_context_get_max_completed_connections (connections->context))
{
dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
"The maximum number of active connections has been reached");
return FALSE;
}
if (dbus_connection_get_unix_user (requesting_completion, &uid))
{
if (get_connections_for_uid (connections, uid) >=
bus_context_get_max_connections_per_user (connections->context))
{
dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
"The maximum number of active connections for UID %lu has been reached",
uid);
return FALSE;
}
}
return TRUE;
}
static void
bus_pending_reply_free (BusPendingReply *pending)
{
_dbus_verbose ("Freeing pending reply %p, replier %p receiver %p serial %u\n",
pending,
pending->will_send_reply,
pending->will_get_reply,
pending->reply_serial);
dbus_free (pending);
}
static dbus_bool_t
bus_pending_reply_send_no_reply (BusConnections *connections,
BusTransaction *transaction,
BusPendingReply *pending)
{
DBusMessage *message;
DBusMessageIter iter;
dbus_bool_t retval;
const char *errmsg;
retval = FALSE;
message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR);
if (message == NULL)
return FALSE;
dbus_message_set_no_reply (message, TRUE);
if (!dbus_message_set_reply_serial (message,
pending->reply_serial))
goto out;
if (!dbus_message_set_error_name (message,
DBUS_ERROR_NO_REPLY))
goto out;
errmsg = "Message did not receive a reply (timeout by message bus)";
dbus_message_iter_init_append (message, &iter);
if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &errmsg))
goto out;
if (!bus_transaction_send_from_driver (transaction, pending->will_get_reply,
message))
goto out;
retval = TRUE;
out:
dbus_message_unref (message);
return retval;
}
static dbus_bool_t
bus_pending_reply_expired (BusExpireList *list,
DBusList *link,
void *data)
{
BusPendingReply *pending = link->data;
BusConnections *connections = data;
BusTransaction *transaction;
/* No reply is forthcoming. So nuke it if we can. If not,
* leave it in the list to try expiring again later when we
* get more memory.
*/
_dbus_verbose ("Expiring pending reply %p, replier %p receiver %p serial %u\n",
pending,
pending->will_send_reply,
pending->will_get_reply,
pending->reply_serial);
transaction = bus_transaction_new (connections->context);
if (transaction == NULL)
return FALSE;
if (!bus_pending_reply_send_no_reply (connections,
transaction,
pending))
{
bus_transaction_cancel_and_free (transaction);
return FALSE;
}
bus_expire_list_remove_link (connections->pending_replies, link);
bus_pending_reply_free (pending);
bus_transaction_execute_and_free (transaction);
return TRUE;
}
static void
bus_connection_drop_pending_replies (BusConnections *connections,
DBusConnection *connection)
{
/* The DBusConnection is almost 100% finalized here, so you can't
* do anything with it except check for pointer equality
*/
DBusList *link;
_dbus_verbose ("Dropping pending replies that involve connection %p\n",
connection);
link = bus_expire_list_get_first_link (connections->pending_replies);
while (link != NULL)
{
DBusList *next;
BusPendingReply *pending;
next = bus_expire_list_get_next_link (connections->pending_replies,
link);
pending = link->data;
if (pending->will_get_reply == connection)
{
/* We don't need to track this pending reply anymore */
_dbus_verbose ("Dropping pending reply %p, replier %p receiver %p serial %u\n",
pending,
pending->will_send_reply,
pending->will_get_reply,
pending->reply_serial);
bus_expire_list_remove_link (connections->pending_replies,
link);
bus_pending_reply_free (pending);
}
else if (pending->will_send_reply == connection)
{
/* The reply isn't going to be sent, so set things
* up so it will be expired right away
*/
_dbus_verbose ("Will expire pending reply %p, replier %p receiver %p serial %u\n",
pending,
pending->will_send_reply,
pending->will_get_reply,
pending->reply_serial);
pending->will_send_reply = NULL;
pending->expire_item.added_tv_sec = 0;
pending->expire_item.added_tv_usec = 0;
bus_expire_list_recheck_immediately (connections->pending_replies);
}
link = next;
}
}
typedef struct
{
BusPendingReply *pending;
BusConnections *connections;
} CancelPendingReplyData;
static void
cancel_pending_reply (void *data)
{
CancelPendingReplyData *d = data;
_dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
if (!bus_expire_list_remove (d->connections->pending_replies,
&d->pending->expire_item))
_dbus_assert_not_reached ("pending reply did not exist to be cancelled");
bus_pending_reply_free (d->pending); /* since it's been cancelled */
}
static void
cancel_pending_reply_data_free (void *data)
{
CancelPendingReplyData *d = data;
_dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
/* d->pending should be either freed or still
* in the list of pending replies (owned by someone
* else)
*/
dbus_free (d);
}
/*
* Record that a reply is allowed; return TRUE on success.
*/
dbus_bool_t
bus_connections_expect_reply (BusConnections *connections,
BusTransaction *transaction,
DBusConnection *will_get_reply,
DBusConnection *will_send_reply,
DBusMessage *reply_to_this,
DBusError *error)
{
BusPendingReply *pending;
dbus_uint32_t reply_serial;
DBusList *link;
CancelPendingReplyData *cprd;
int count;
_dbus_assert (will_get_reply != NULL);
_dbus_assert (will_send_reply != NULL);
_dbus_assert (reply_to_this != NULL);
if (dbus_message_get_no_reply (reply_to_this))
return TRUE; /* we won't allow a reply, since client doesn't care for one. */
reply_serial = dbus_message_get_serial (reply_to_this);
link = bus_expire_list_get_first_link (connections->pending_replies);
count = 0;
while (link != NULL)
{
pending = link->data;
if (pending->reply_serial == reply_serial &&
pending->will_get_reply == will_get_reply &&
pending->will_send_reply == will_send_reply)
{
dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
"Message has the same reply serial as a currently-outstanding existing method call");
return FALSE;
}
link = bus_expire_list_get_next_link (connections->pending_replies,
link);
if (pending->will_get_reply == will_get_reply)
++count;
}
if (count >=
bus_context_get_max_replies_per_connection (connections->context))
{
dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
"The maximum number of pending replies per connection has been reached");
return FALSE;
}
pending = dbus_new0 (BusPendingReply, 1);
if (pending == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
#ifdef DBUS_ENABLE_VERBOSE_MODE
/* so we can see a not-yet-added pending reply */
pending->expire_item.added_tv_sec = 1;
pending->expire_item.added_tv_usec = 1;
#endif
pending->will_get_reply = will_get_reply;
pending->will_send_reply = will_send_reply;
pending->reply_serial = reply_serial;
cprd = dbus_new0 (CancelPendingReplyData, 1);
if (cprd == NULL)
{
BUS_SET_OOM (error);
bus_pending_reply_free (pending);
return FALSE;
}
if (!bus_expire_list_add (connections->pending_replies,
&pending->expire_item))
{
BUS_SET_OOM (error);
dbus_free (cprd);
bus_pending_reply_free (pending);
return FALSE;
}
if (!bus_transaction_add_cancel_hook (transaction,
cancel_pending_reply,
cprd,
cancel_pending_reply_data_free))
{
BUS_SET_OOM (error);
bus_expire_list_remove (connections->pending_replies, &pending->expire_item);
dbus_free (cprd);
bus_pending_reply_free (pending);
return FALSE;
}
cprd->pending = pending;
cprd->connections = connections;
_dbus_get_current_time (&pending->expire_item.added_tv_sec,
&pending->expire_item.added_tv_usec);
_dbus_verbose ("Added pending reply %p, replier %p receiver %p serial %u\n",
pending,
pending->will_send_reply,
pending->will_get_reply,
pending->reply_serial);
return TRUE;
}
typedef struct
{
DBusList *link;
BusConnections *connections;
} CheckPendingReplyData;
static void
cancel_check_pending_reply (void *data)
{
CheckPendingReplyData *d = data;
_dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
bus_expire_list_add_link (d->connections->pending_replies,
d->link);
d->link = NULL;
}
static void
check_pending_reply_data_free (void *data)
{
CheckPendingReplyData *d = data;
_dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
if (d->link != NULL)
{
BusPendingReply *pending = d->link->data;
_dbus_assert (!bus_expire_list_contains_item (d->connections->pending_replies,
&pending->expire_item));
bus_pending_reply_free (pending);
_dbus_list_free_link (d->link);
}
dbus_free (d);
}
/*
* Check whether a reply is allowed, remove BusPendingReply
* if so, return TRUE if so.
*/
dbus_bool_t
bus_connections_check_reply (BusConnections *connections,
BusTransaction *transaction,
DBusConnection *sending_reply,
DBusConnection *receiving_reply,
DBusMessage *reply,
DBusError *error)
{
CheckPendingReplyData *cprd;
DBusList *link;
dbus_uint32_t reply_serial;
_dbus_assert (sending_reply != NULL);
_dbus_assert (receiving_reply != NULL);
reply_serial = dbus_message_get_reply_serial (reply);
link = bus_expire_list_get_first_link (connections->pending_replies);
while (link != NULL)
{
BusPendingReply *pending = link->data;
if (pending->reply_serial == reply_serial &&
pending->will_get_reply == receiving_reply &&
pending->will_send_reply == sending_reply)
{
_dbus_verbose ("Found pending reply with serial %u\n", reply_serial);
break;
}
link = bus_expire_list_get_next_link (connections->pending_replies,
link);
}
if (link == NULL)
{
_dbus_verbose ("No pending reply expected\n");
return FALSE;
}
cprd = dbus_new0 (CheckPendingReplyData, 1);
if (cprd == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
if (!bus_transaction_add_cancel_hook (transaction,
cancel_check_pending_reply,
cprd,
check_pending_reply_data_free))
{
BUS_SET_OOM (error);
dbus_free (cprd);
return FALSE;
}
cprd->link = link;
cprd->connections = connections;
bus_expire_list_unlink (connections->pending_replies,
link);
_dbus_assert (!bus_expire_list_contains_item (connections->pending_replies, link->data));
return TRUE;
}
/*
* 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;
DBusMessage *message;
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
message_to_send_free (DBusConnection *connection,
MessageToSend *to_send)
{
if (to_send->message)
dbus_message_unref (to_send->message);
if (to_send->preallocated)
dbus_connection_free_preallocated_send (connection, to_send->preallocated);
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)
{
BusTransaction *transaction;
transaction = dbus_new0 (BusTransaction, 1);
if (transaction == NULL)
return NULL;
transaction->context = context;
return transaction;
}
BusContext*
bus_transaction_get_context (BusTransaction *transaction)
{
return transaction->context;
}
BusConnections*
bus_transaction_get_connections (BusTransaction *transaction)
{
return bus_context_get_connections (transaction->context);
}
dbus_bool_t
bus_transaction_send_from_driver (BusTransaction *transaction,
DBusConnection *connection,
DBusMessage *message)
{
/* We have to set the sender to the driver, and have
* to check security policy since it was not done in
* dispatch.c
*/
_dbus_verbose ("Sending %s %s %s from driver\n",
dbus_message_get_interface (message) ?
dbus_message_get_interface (message) : "(no interface)",
dbus_message_get_member (message) ?
dbus_message_get_member (message) : "(no member)",
dbus_message_get_error_name (message) ?
dbus_message_get_error_name (message) : "(no error name)");
if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS))
return FALSE;
if (bus_connection_is_active (connection))
{
if (!dbus_message_set_destination (message,
bus_connection_get_name (connection)))
return FALSE;
}
/* bus driver never wants a reply */
dbus_message_set_no_reply (message, TRUE);
/* If security policy doesn't allow the message, we silently
* eat it; the driver doesn't care about getting a reply.
*/
if (!bus_context_check_security_policy (bus_transaction_get_context (transaction),
transaction,
NULL, connection, connection, message, NULL))
return TRUE;
return bus_transaction_send (transaction, connection, message);
}
dbus_bool_t
bus_transaction_send (BusTransaction *transaction,
DBusConnection *connection,
DBusMessage *message)
{
MessageToSend *to_send;
BusConnectionData *d;
DBusList *link;
_dbus_verbose (" trying to add %s interface=%s member=%s error=%s to transaction%s\n",
dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR ? "error" :
dbus_message_get_reply_serial (message) != 0 ? "reply" :
"message",
dbus_message_get_interface (message) ?
dbus_message_get_interface (message) : "(unset)",
dbus_message_get_member (message) ?
dbus_message_get_member (message) : "(unset)",
dbus_message_get_error_name (message) ?
dbus_message_get_error_name (message) : "(unset)",
dbus_connection_get_is_connected (connection) ?
"" : " (disconnected)");
_dbus_assert (dbus_message_get_sender (message) != NULL);
if (!dbus_connection_get_is_connected (connection))
return TRUE; /* silently ignore disconnected connections */
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
to_send = dbus_new (MessageToSend, 1);
if (to_send == NULL)
{
return FALSE;
}
to_send->preallocated = dbus_connection_preallocate_send (connection);
if (to_send->preallocated == NULL)
{
dbus_free (to_send);
return FALSE;
}
dbus_message_ref (message);
to_send->message = message;
to_send->transaction = transaction;
_dbus_verbose ("about to prepend message\n");
if (!_dbus_list_prepend (&d->transaction_messages, to_send))
{
message_to_send_free (connection, to_send);
return FALSE;
}
_dbus_verbose ("prepended message\n");
/* See if we already had this connection in the list
* for this transaction. If we have a pending message,
* then we should already be in transaction->connections
*/
link = _dbus_list_get_first_link (&d->transaction_messages);
_dbus_assert (link->data == to_send);
link = _dbus_list_get_next_link (&d->transaction_messages, link);
while (link != NULL)
{
MessageToSend *m = link->data;
DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link);
if (m->transaction == transaction)
break;
link = next;
}
if (link == NULL)
{
if (!_dbus_list_prepend (&transaction->connections, connection))
{
_dbus_list_remove (&d->transaction_messages, to_send);
message_to_send_free (connection, to_send);
return FALSE;
}
}
return TRUE;
}
static void
connection_cancel_transaction (DBusConnection *connection,
BusTransaction *transaction)
{
DBusList *link;
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
link = _dbus_list_get_first_link (&d->transaction_messages);
while (link != NULL)
{
MessageToSend *m = link->data;
DBusList *next = _dbus_list_get_next_link (&d->transaction_messages, link);
if (m->transaction == transaction)
{
_dbus_list_remove_link (&d->transaction_messages,
link);
message_to_send_free (connection, m);
}
link = next;
}
}
void
bus_transaction_cancel_and_free (BusTransaction *transaction)
{
DBusConnection *connection;
_dbus_verbose ("TRANSACTION: cancelled\n");
while ((connection = _dbus_list_pop_first (&transaction->connections)))
connection_cancel_transaction (connection, transaction);
_dbus_assert (transaction->connections == NULL);
_dbus_list_foreach (&transaction->cancel_hooks,
cancel_hook_cancel, NULL);
free_cancel_hooks (transaction);
dbus_free (transaction);
}
static void
connection_execute_transaction (DBusConnection *connection,
BusTransaction *transaction)
{
DBusList *link;
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
/* Send the queue in order (FIFO) */
link = _dbus_list_get_last_link (&d->transaction_messages);
while (link != NULL)
{
MessageToSend *m = link->data;
DBusList *prev = _dbus_list_get_prev_link (&d->transaction_messages, link);
if (m->transaction == transaction)
{
_dbus_list_remove_link (&d->transaction_messages,
link);
_dbus_assert (dbus_message_get_sender (m->message) != NULL);
dbus_connection_send_preallocated (connection,
m->preallocated,
m->message,
NULL);
m->preallocated = NULL; /* so we don't double-free it */
message_to_send_free (connection, m);
}
link = prev;
}
}
void
bus_transaction_execute_and_free (BusTransaction *transaction)
{
/* For each connection in transaction->connections
* send the messages
*/
DBusConnection *connection;
_dbus_verbose ("TRANSACTION: executing\n");
while ((connection = _dbus_list_pop_first (&transaction->connections)))
connection_execute_transaction (connection, transaction);
_dbus_assert (transaction->connections == NULL);
free_cancel_hooks (transaction);
dbus_free (transaction);
}
static void
bus_connection_remove_transactions (DBusConnection *connection)
{
MessageToSend *to_send;
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
while ((to_send = _dbus_list_get_first (&d->transaction_messages)))
{
/* only has an effect for the first MessageToSend listing this transaction */
_dbus_list_remove (&to_send->transaction->connections,
connection);
_dbus_list_remove (&d->transaction_messages, to_send);
message_to_send_free (connection, to_send);
}
}
/**
* Converts the DBusError to a message reply
*/
dbus_bool_t
bus_transaction_send_error_reply (BusTransaction *transaction,
DBusConnection *connection,
const DBusError *error,
DBusMessage *in_reply_to)
{
DBusMessage *reply;
_dbus_assert (error != NULL);
_DBUS_ASSERT_ERROR_IS_SET (error);
_dbus_verbose ("Sending error reply %s \"%s\"\n",
error->name, error->message);
reply = dbus_message_new_error (in_reply_to,
error->name,
error->message);
if (reply == NULL)
return FALSE;
if (!bus_transaction_send_from_driver (transaction, connection, reply))
{
dbus_message_unref (reply);
return FALSE;
}
dbus_message_unref (reply);
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;
_dbus_verbose (" adding cancel hook function = %p data = %p\n",
cancel_function, data);
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;
}