mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2026-05-16 18:58:06 +02:00
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.
2219 lines
62 KiB
C
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;
|
|
}
|