From 041b0767b284034aee09e9a0de2a3844b8cc546a Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Mon, 25 Nov 2002 05:13:09 +0000 Subject: 2002-11-24 Havoc Pennington * test/echo-client.c, test/echo-server.c: cheesy test clients. * configure.in (AC_CHECK_FUNCS): check for writev * dbus/dbus-message.c (_dbus_message_get_network_data): new function * dbus/dbus-list.c (_dbus_list_foreach): new function * dbus/dbus-internals.c (_dbus_verbose): new function * dbus/dbus-server.c, dbus/dbus-server.h: public object representing a server that listens for connections. * dbus/.cvsignore: create * dbus/dbus-errors.h, dbus/dbus-errors.c: public API for reporting errors * dbus/dbus-connection.h, dbus/dbus-connection.c: public object representing a connection that sends/receives messages. (Same object used for both client and server.) * dbus/dbus-transport.h, dbus/dbus-transport.c: Basic abstraction for different kinds of stream that we might read/write messages from. --- .cvsignore | 16 ++ ChangeLog | 31 ++ bus/.cvsignore | 7 + configure.in | 9 + dbus/.cvsignore | 7 + dbus/Makefile.am | 24 +- dbus/dbus-connection-internal.h | 64 +++++ dbus/dbus-connection.c | 623 ++++++++++++++++++++++++++++++++++++++++ dbus/dbus-connection.h | 100 +++++++ dbus/dbus-errors.c | 109 +++++++ dbus/dbus-errors.h | 62 ++++ dbus/dbus-internals.c | 196 ++++++++++++- dbus/dbus-internals.h | 23 +- dbus/dbus-list.c | 28 ++ dbus/dbus-list.h | 5 +- dbus/dbus-message-internal.h | 56 ++++ dbus/dbus-message.c | 325 ++++++++++++++++++++- dbus/dbus-message.h | 1 + dbus/dbus-server-protected.h | 79 +++++ dbus/dbus-server-unix.c | 303 +++++++++++++++++++ dbus/dbus-server-unix.h | 37 +++ dbus/dbus-server.c | 299 +++++++++++++++++++ dbus/dbus-server.h | 66 +++++ dbus/dbus-transport-protected.h | 88 ++++++ dbus/dbus-transport-unix.c | 581 +++++++++++++++++++++++++++++++++++++ dbus/dbus-transport-unix.h | 37 +++ dbus/dbus-transport.c | 288 +++++++++++++++++++ dbus/dbus-transport.h | 53 ++++ dbus/dbus-types.h | 33 ++- dbus/dbus-watch.c | 381 ++++++++++++++++++++++++ dbus/dbus-watch.h | 60 ++++ dbus/dbus.h | 5 +- doc/.cvsignore | 8 + test/.cvsignore | 9 + test/Makefile.am | 19 ++ test/echo-client.c | 41 +++ test/echo-server.c | 48 ++++ test/watch.c | 268 +++++++++++++++++ test/watch.h | 15 + 39 files changed, 4389 insertions(+), 15 deletions(-) create mode 100644 .cvsignore create mode 100644 bus/.cvsignore create mode 100644 dbus/.cvsignore create mode 100644 dbus/dbus-connection-internal.h create mode 100644 dbus/dbus-connection.c create mode 100644 dbus/dbus-connection.h create mode 100644 dbus/dbus-errors.c create mode 100644 dbus/dbus-errors.h create mode 100644 dbus/dbus-message-internal.h create mode 100644 dbus/dbus-server-protected.h create mode 100644 dbus/dbus-server-unix.c create mode 100644 dbus/dbus-server-unix.h create mode 100644 dbus/dbus-server.c create mode 100644 dbus/dbus-server.h create mode 100644 dbus/dbus-transport-protected.h create mode 100644 dbus/dbus-transport-unix.c create mode 100644 dbus/dbus-transport-unix.h create mode 100644 dbus/dbus-transport.c create mode 100644 dbus/dbus-transport.h create mode 100644 dbus/dbus-watch.c create mode 100644 dbus/dbus-watch.h create mode 100644 doc/.cvsignore create mode 100644 test/.cvsignore create mode 100644 test/echo-client.c create mode 100644 test/echo-server.c create mode 100644 test/watch.c create mode 100644 test/watch.h diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 00000000..7487721d --- /dev/null +++ b/.cvsignore @@ -0,0 +1,16 @@ +config.log +config.status +config.sub +configure +*.pc +libtool +ltmain.sh +stamp-h1 +Doxyfile +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +config.guess +config.h +config.h.in diff --git a/ChangeLog b/ChangeLog index d0a4b1d9..e659c748 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2002-11-24 Havoc Pennington + + * test/echo-client.c, test/echo-server.c: cheesy test + clients. + + * configure.in (AC_CHECK_FUNCS): check for writev + + * dbus/dbus-message.c (_dbus_message_get_network_data): new + function + + * dbus/dbus-list.c (_dbus_list_foreach): new function + + * dbus/dbus-internals.c (_dbus_verbose): new function + + * dbus/dbus-server.c, dbus/dbus-server.h: public object + representing a server that listens for connections. + + * dbus/.cvsignore: create + + * dbus/dbus-errors.h, dbus/dbus-errors.c: + public API for reporting errors + + * dbus/dbus-connection.h, dbus/dbus-connection.c: + public object representing a connection that + sends/receives messages. (Same object used for + both client and server.) + + * dbus/dbus-transport.h, dbus/dbus-transport.c: + Basic abstraction for different kinds of stream + that we might read/write messages from. + 2002-11-23 Havoc Pennington * dbus/dbus-internals.h (_DBUS_INT_MAX): add _DBUS_INT_MIN diff --git a/bus/.cvsignore b/bus/.cvsignore new file mode 100644 index 00000000..8b966130 --- /dev/null +++ b/bus/.cvsignore @@ -0,0 +1,7 @@ +.deps +.libs +Makefile +Makefile.in +*.lo +*.la +dbus-daemon-1 diff --git a/configure.in b/configure.in index d8ba3c6a..4440040d 100644 --- a/configure.in +++ b/configure.in @@ -102,6 +102,10 @@ AC_C_BIGENDIAN AC_CHECK_FUNCS(vsnprintf vasprintf) +dnl check for writev header and writev function so we're +dnl good to go if HAVE_WRITEV gets defined. +AC_CHECK_HEADERS(sys/uio.h, [AC_CHECK_FUNCS(writev)]) + DBUS_CLIENT_CFLAGS= DBUS_CLIENT_LIBS= AC_SUBST(DBUS_CLIENT_CFLAGS) @@ -112,6 +116,11 @@ DBUS_BUS_LIBS= AC_SUBST(DBUS_BUS_CFLAGS) AC_SUBST(DBUS_BUS_LIBS) +DBUS_TEST_CFLAGS= +DBUS_TEST_LIBS= +AC_SUBST(DBUS_TEST_CFLAGS) +AC_SUBST(DBUS_TEST_LIBS) + AC_OUTPUT([ Makefile Doxyfile diff --git a/dbus/.cvsignore b/dbus/.cvsignore new file mode 100644 index 00000000..4ebd42b8 --- /dev/null +++ b/dbus/.cvsignore @@ -0,0 +1,7 @@ +.deps +.libs +Makefile +Makefile.in +*.lo +*.la +dbus-test diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 46d703f0..f830bb7b 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -7,18 +7,38 @@ lib_LTLIBRARIES=libdbus-1.la dbusinclude_HEADERS= \ dbus.h \ + dbus-errors.h \ dbus-macros.h \ dbus-memory.h \ dbus-message.h \ + dbus-server.h \ dbus-types.h libdbus_1_la_SOURCES= \ + dbus-connection.c \ + dbus-connection-internal.h \ + dbus-errors.c \ dbus-memory.c \ - dbus-message.c + dbus-message.c \ + dbus-message-internal.h \ + dbus-server.c \ + dbus-server-protected.h \ + dbus-server-unix.c \ + dbus-server-unix.h \ + dbus-transport.c \ + dbus-transport.h \ + dbus-transport-protected.h \ + dbus-transport-unix.c \ + dbus-transport-unix.h \ + dbus-watch.c \ + dbus-watch.h + ## this library is linked into both libdbus and the bus ## itself, but does not export any symbols from libdbus. -## It contains utility functions not in the public API. +## i.e. the point is to contain symbols that we don't +## want the shared lib to export, but we do want the +## message bus to be able to use. noinst_LTLIBRARIES=libdbus-convenience.la diff --git a/dbus/dbus-connection-internal.h b/dbus/dbus-connection-internal.h new file mode 100644 index 00000000..7260bde0 --- /dev/null +++ b/dbus/dbus-connection-internal.h @@ -0,0 +1,64 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-connection-internal.h DBusConnection internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_CONNECTION_INTERNAL_H +#define DBUS_CONNECTION_INTERNAL_H + +#include +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef enum +{ + DBUS_ITERATION_DO_WRITING = 1 << 0, /**< Write messages out. */ + DBUS_ITERATION_DO_READING = 1 << 1, /**< Read messages in. */ + DBUS_ITERATION_BLOCK = 1 << 2 /**< Block if nothing to do. */ +} DBusIterationFlags; + +dbus_bool_t _dbus_connection_queue_received_message (DBusConnection *connection, + DBusMessage *message); +dbus_bool_t _dbus_connection_have_messages_to_send (DBusConnection *connection); + +DBusMessage* _dbus_connection_get_message_to_send (DBusConnection *connection); +void _dbus_connection_message_sent (DBusConnection *connection, + DBusMessage *message); + +dbus_bool_t _dbus_connection_add_watch (DBusConnection *connection, + DBusWatch *watch); +void _dbus_connection_remove_watch (DBusConnection *connection, + DBusWatch *watch); +DBusConnection* _dbus_connection_new_for_transport (DBusTransport *transport); + + +void _dbus_connection_do_iteration (DBusConnection *connection, + unsigned int flags, + int timeout_milliseconds); + +void _dbus_connection_transport_error (DBusConnection *connection, + DBusResultCode result_code); + +DBUS_END_DECLS; + +#endif /* DBUS_CONNECTION_INTERNAL_H */ diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c new file mode 100644 index 00000000..f60e53a9 --- /dev/null +++ b/dbus/dbus-connection.c @@ -0,0 +1,623 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-connection.c DBusConnection object + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dbus-connection.h" +#include "dbus-list.h" +#include "dbus-transport.h" +#include "dbus-watch.h" +#include "dbus-connection-internal.h" +#include "dbus-list.h" + +/** + * @defgroup DBusConnection DBusConnection + * @ingroup DBus + * @brief Connection to another application + * + * A DBusConnection represents a connection to another + * application. Messages can be sent and received via this connection. + * + * The connection maintains a queue of incoming messages and a queue + * of outgoing messages. dbus_connection_pop_message() and friends + * can be used to read incoming messages from the queue. + * Outgoing messages are automatically discarded as they are + * written to the network. + * + * In brief a DBusConnection is a message queue associated with some + * message transport mechanism such as a socket. + * + */ + +/** + * @defgroup DBusConnectionInternals DBusConnection implementation details + * @ingroup DBusInternals + * @brief Implementation details of DBusConnection + * + * @{ + */ + +/** + * Implementation details of DBusConnection. All fields are private. + */ +struct DBusConnection +{ + int refcount; /**< Reference count. */ + + DBusList *outgoing_messages; /**< Queue of messages we need to send, send the end of the list first. */ + DBusList *incoming_messages; /**< Queue of messages we have received, end of the list received most recently. */ + + int n_outgoing; /**< Length of outgoing queue. */ + int n_incoming; /**< Length of incoming queue. */ + + DBusTransport *transport; /**< Object that sends/receives messages over network. */ + DBusWatchList *watches; /**< Stores active watches. */ + + DBusConnectionErrorFunction error_function; /**< Callback for errors. */ + void *error_data; /**< Data for error callback. */ + DBusFreeFunction error_free_data_function; /**< Free function for error callback data. */ +}; + +/** + * Adds a message to the incoming message queue, returning #FALSE + * if there's insufficient memory to queue the message. + * + * @param connection the connection. + * @param message the message to queue. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_connection_queue_received_message (DBusConnection *connection, + DBusMessage *message) +{ + if (!_dbus_list_append (&connection->incoming_messages, + message)) + return FALSE; + + dbus_message_ref (message); + connection->n_incoming += 1; + + return TRUE; +} + +/** + * Checks whether there are messages in the outgoing message queue. + * + * @param connection the connection. + * @returns #TRUE if the outgoing queue is non-empty. + */ +dbus_bool_t +_dbus_connection_have_messages_to_send (DBusConnection *connection) +{ + return connection->outgoing_messages != NULL; +} + +/** + * Gets the next outgoing message. The message remanins in the + * queue, and the caller does not own a reference to it. + * + * @param connection the connection. + * @returns the message to be sent. + */ +DBusMessage* +_dbus_connection_get_message_to_send (DBusConnection *connection) +{ + return _dbus_list_get_last (&connection->outgoing_messages); +} + +/** + * Notifies the connection that a message has been sent, so the + * message can be removed from the outgoing queue. + * + * @param connection the connection. + * @param message the message that was sent. + */ +void +_dbus_connection_message_sent (DBusConnection *connection, + DBusMessage *message) +{ + _dbus_assert (message == _dbus_list_get_last (&connection->outgoing_messages)); + _dbus_list_pop_last (&connection->outgoing_messages); + dbus_message_unref (message); + + connection->n_outgoing -= 1; + if (connection->n_outgoing == 0) + _dbus_transport_messages_pending (connection->transport, + connection->n_outgoing); +} + +/** + * Adds a watch using the connection's DBusAddWatchFunction if + * available. Otherwise records the watch to be added when said + * function is available. Also re-adds the watch if the + * DBusAddWatchFunction changes. May fail due to lack of memory. + * + * @param connection the connection. + * @param watch the watch to add. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_connection_add_watch (DBusConnection *connection, + DBusWatch *watch) +{ + return _dbus_watch_list_add_watch (connection->watches, + watch); + + return TRUE; +} + +/** + * Removes a watch using the connection's DBusRemoveWatchFunction + * if available. It's an error to call this function on a watch + * that was not previously added. + * + * @param connection the connection. + * @param watch the watch to remove. + */ +void +_dbus_connection_remove_watch (DBusConnection *connection, + DBusWatch *watch) +{ + _dbus_watch_list_remove_watch (connection->watches, + watch); +} + +static void +handle_error (DBusConnection *connection, + DBusResultCode result) +{ + if (result != DBUS_RESULT_SUCCESS && + connection->error_function != NULL) + { + dbus_connection_ref (connection); + (* connection->error_function) (connection, result, + connection->error_data); + dbus_connection_unref (connection); + } +} + +static void +set_result_handled (DBusConnection *connection, + DBusResultCode *result_address, + DBusResultCode result) +{ + dbus_set_result (result_address, result); + handle_error (connection, result); +} + +/** + * Reports a transport error to the connection. Typically + * results in an application error callback being invoked. + * + * @param connection the connection. + * @param result_code the error code. + */ +void +_dbus_connection_transport_error (DBusConnection *connection, + DBusResultCode result_code) +{ + handle_error (connection, result_code); +} + +/** + * Queues incoming messages and sends outgoing messages for this + * connection, optionally blocking in the process. Each call to + * _dbus_connection_do_iteration() will call select() or poll() one + * time and then read or write data if possible. + * + * The purpose of this function is to be able to flush outgoing + * messages or queue up incoming messages without returning + * control to the application and causing reentrancy weirdness. + * + * The flags parameter allows you to specify whether to + * read incoming messages, write outgoing messages, or both, + * and whether to block if no immediate action is possible. + * + * The timeout_milliseconds parameter does nothing unless the + * iteration is blocking. + * + * If there are no outgoing messages and DBUS_ITERATION_DO_READING + * wasn't specified, then it's impossible to block, even if + * you specify DBUS_ITERATION_BLOCK; in that case the function + * returns immediately. + * + * @param connection the connection. + * @param flags iteration flags. + * @param timeout_milliseconds maximum blocking time, or -1 for no limit. + */ +void +_dbus_connection_do_iteration (DBusConnection *connection, + unsigned int flags, + int timeout_milliseconds) +{ + if (connection->n_outgoing == 0) + flags &= ~DBUS_ITERATION_DO_WRITING; + + _dbus_transport_do_iteration (connection->transport, + flags, timeout_milliseconds); +} + +/** + * Creates a new connection for the given transport. A transport + * represents a message stream that uses some concrete mechanism, such + * as UNIX domain sockets. May return #NULL if insufficient + * memory exists to create the connection. + * + * @param transport the transport. + * @returns the new connection, or #NULL on failure. + */ +DBusConnection* +_dbus_connection_new_for_transport (DBusTransport *transport) +{ + DBusConnection *connection; + DBusWatchList *watch_list; + + watch_list = NULL; + connection = NULL; + + watch_list = _dbus_watch_list_new (); + if (watch_list == NULL) + goto error; + + connection = dbus_new0 (DBusConnection, 1); + if (connection == NULL) + goto error; + + connection->refcount = 1; + connection->transport = transport; + connection->watches = watch_list; + + _dbus_transport_ref (transport); + _dbus_transport_set_connection (transport, connection); + + return connection; + + error: + + _dbus_assert (connection == NULL); + + if (watch_list) + _dbus_watch_list_free (watch_list); + + return NULL; +} + +/** @} */ + +/** + * @addtogroup DBusConnection + * + * @{ + */ + +/** + * Opens a new connection to a remote address. + * + * @todo specify what the address parameter is. Right now + * it's just the name of a UNIX domain socket. It should be + * something more complex that encodes which transport to use. + * + * If the open fails, the function returns #NULL, and provides + * a reason for the failure in the result parameter. Pass + * #NULL for the result parameter if you aren't interested + * in the reason for failure. + * + * @param address the address. + * @param result address where a result code can be returned. + * @returns new connection, or #NULL on failure. + */ +DBusConnection* +dbus_connection_open (const char *address, + DBusResultCode *result) +{ + DBusConnection *connection; + DBusTransport *transport; + + transport = _dbus_transport_open (address, result); + if (transport == NULL) + return NULL; + + connection = _dbus_connection_new_for_transport (transport); + + _dbus_transport_unref (transport); + + if (connection == NULL) + { + dbus_set_result (result, DBUS_RESULT_NO_MEMORY); + return NULL; + } + + return connection; +} + +/** + * Increments the reference count of a DBusConnection. + * + * @param connection the connection. + */ +void +dbus_connection_ref (DBusConnection *connection) +{ + connection->refcount += 1; +} + +/** + * Decrements the reference count of a DBusConnection, and finalizes + * it if the count reaches zero. If a connection is still connected + * when it's finalized, it will be disconnected (that is, associated + * file handles will be closed). + * + * @param connection the connection. + */ +void +dbus_connection_unref (DBusConnection *connection) +{ + _dbus_assert (connection != NULL); + _dbus_assert (connection->refcount > 0); + + connection->refcount -= 1; + if (connection->refcount == 0) + { + /* free error data as a side effect */ + dbus_connection_set_error_function (connection, + NULL, NULL, NULL); + + _dbus_watch_list_free (connection->watches); + + _dbus_list_foreach (&connection->outgoing_messages, + (DBusForeachFunction) dbus_message_unref, + NULL); + _dbus_list_clear (&connection->outgoing_messages); + + _dbus_list_foreach (&connection->incoming_messages, + (DBusForeachFunction) dbus_message_unref, + NULL); + _dbus_list_clear (&connection->incoming_messages); + + _dbus_transport_unref (connection->transport); + + dbus_free (connection); + } +} + +/** + * Closes the connection, so no further data can be sent or received. + * Any further attempts to send data will result in errors. This + * function does not affect the connection's reference count. It's + * safe to disconnect a connection more than once; all calls after the + * first do nothing. It's impossible to "reconnect" a connection, a + * new connection must be created. + * + * @param connection the connection. + */ +void +dbus_connection_disconnect (DBusConnection *connection) +{ + _dbus_transport_disconnect (connection->transport); +} + +/** + * Gets whether the connection is currently connected. All + * connections are connected when they are opened. A connection may + * become disconnected when the remote application closes its end, or + * exits; a connection may also be disconnected with + * dbus_connection_disconnect(). + * + * @param connection the connection. + * @returns #TRUE if the connection is still alive. + */ +dbus_bool_t +dbus_connection_get_is_connected (DBusConnection *connection) +{ + return _dbus_transport_get_is_connected (connection->transport); +} + +/** + * Adds a message to the outgoing message queue. Does not block to + * write the message to the network; that happens asynchronously. to + * force the message to be written, call dbus_connection_flush(). + * + * If the function fails, it returns #FALSE and returns the + * reason for failure via the result parameter. + * The result parameter can be #NULL if you aren't interested + * in the reason for the failure. + * + * @param connection the connection. + * @param message the message to write. + * @param result address where result code can be placed. + * @returns #TRUE on success. + */ +dbus_bool_t +dbus_connection_send_message (DBusConnection *connection, + DBusMessage *message, + DBusResultCode *result) +{ + if (!_dbus_list_prepend (&connection->outgoing_messages, + message)) + { + set_result_handled (connection, result, DBUS_RESULT_NO_MEMORY); + return FALSE; + } + + dbus_message_ref (message); + connection->n_outgoing += 1; + + _dbus_message_lock (message); + + if (connection->n_outgoing == 1) + _dbus_transport_messages_pending (connection->transport, + connection->n_outgoing); +} + +/** + * Blocks until the outgoing message queue is empty. + * + * @param connection the connection. + */ +void +dbus_connection_flush (DBusConnection *connection) +{ + while (connection->n_outgoing > 0) + _dbus_connection_do_iteration (connection, + DBUS_ITERATION_DO_WRITING | + DBUS_ITERATION_BLOCK, + -1); +} + +/** + * Gets the number of messages in the incoming message queue. + * + * @param connection the connection. + * @returns the number of messages in the queue. + */ +int +dbus_connection_get_n_messages (DBusConnection *connection) +{ + return connection->n_incoming; +} + +/** + * Returns the first-received message from the incoming message queue, + * leaving it in the queue. The caller does not own a reference to the + * returned message. If the queue is empty, returns #NULL. + * + * @param connection the connection. + * @returns next message in the incoming queue. + */ +DBusMessage* +dbus_connection_peek_message (DBusConnection *connection) +{ + return _dbus_list_get_first (&connection->incoming_messages); +} + +/** + * Returns the first-received message from the incoming message queue, + * removing it from the queue. The caller owns a reference to the + * returned message. If the queue is empty, returns #NULL. + * + * @param connection the connection. + * @returns next message in the incoming queue. + */ +DBusMessage* +dbus_connection_pop_message (DBusConnection *connection) +{ + if (connection->n_incoming > 0) + { + connection->n_incoming -= 1; + return _dbus_list_pop_first (&connection->incoming_messages); + } + else + return NULL; +} + +/** + * Sets the error handler function for the connection. + * + * @param connection the connection. + * @param error_function the error handler. + * @param data data to pass to the error handler. + * @param free_data_function function to be called to free the data. + */ +void +dbus_connection_set_error_function (DBusConnection *connection, + DBusConnectionErrorFunction error_function, + void *data, + DBusFreeFunction free_data_function) +{ + if (connection->error_free_data_function != NULL) + (* connection->error_free_data_function) (connection->error_data); + + connection->error_function = error_function; + connection->error_data = data; + connection->error_free_data_function = free_data_function; +} + +/** + * Sets the watch functions for the connection. These functions are + * responsible for making the application's main loop aware of file + * descriptors that need to be monitored for events, using select() or + * poll(). When using Qt, typically the DBusAddWatchFunction would + * create a QSocketNotifier. When using GLib, the DBusAddWatchFunction + * could call g_io_add_watch(), or could be used as part of a more + * elaborate GSource. + * + * The DBusWatch can be queried for the file descriptor to watch using + * dbus_watch_get_fd(), and for the events to watch for using + * dbus_watch_get_flags(). The flags returned by + * dbus_watch_get_flags() will only contain DBUS_WATCH_READABLE and + * DBUS_WATCH_WRITABLE, never DBUS_WATCH_HANGUP or DBUS_WATCH_ERROR; + * all watches implicitly include a watch for hangups, errors, and + * other exceptional conditions. + * + * Once a file descriptor becomes readable or writable, or an exception + * occurs, dbus_connection_handle_watch() should be called to + * notify the connection of the file descriptor's condition. + * + * dbus_connection_handle_watch() cannot be called during the + * DBusAddWatchFunction, as the connection will not be ready to handle + * that watch yet. + * + * It is not allowed to reference a DBusWatch after it has been passed + * to remove_function. + * + * @param connection the connection. + * @param add_function function to begin monitoring a new descriptor. + * @param remove_function function to stop monitoring a descriptor. + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + */ +void +dbus_connection_set_watch_functions (DBusConnection *connection, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function) +{ + /* ref connection for slightly better reentrancy */ + dbus_connection_ref (connection); + + _dbus_watch_list_set_functions (connection->watches, + add_function, remove_function, + data, free_data_function); + + /* drop our paranoid refcount */ + dbus_connection_unref (connection); +} + +/** + * Called to notify the connection when a previously-added watch + * is ready for reading or writing, or has an exception such + * as a hangup. + * + * @param connection the connection. + * @param watch the watch. + * @param condition the current condition of the file descriptors being watched. + */ +void +dbus_connection_handle_watch (DBusConnection *connection, + DBusWatch *watch, + unsigned int condition) +{ + _dbus_transport_handle_watch (connection->transport, + watch, condition); +} + +/** @} */ diff --git a/dbus/dbus-connection.h b/dbus/dbus-connection.h new file mode 100644 index 00000000..ceb5728f --- /dev/null +++ b/dbus/dbus-connection.h @@ -0,0 +1,100 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-connection.h DBusConnection object + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_CONNECTION_H +#define DBUS_CONNECTION_H + +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusConnection DBusConnection; +typedef struct DBusWatch DBusWatch; + +typedef enum +{ + DBUS_WATCH_READABLE = 1 << 0, /**< As in POLLIN */ + DBUS_WATCH_WRITABLE = 1 << 1, /**< As in POLLOUT */ + DBUS_WATCH_ERROR = 1 << 2, /**< As in POLLERR (can't watch for this, but + * the flag can be passed to dbus_connection_handle_watch()). + */ + DBUS_WATCH_HANGUP = 1 << 3 /**< As in POLLHUP (can't watch for it, but + * can be present in current state). */ +} DBusWatchFlags; + +typedef void (* DBusAddWatchFunction) (DBusWatch *watch, + void *data); + +typedef void (* DBusRemoveWatchFunction) (DBusWatch *watch, + void *data); + +typedef void (* DBusConnectionErrorFunction) (DBusConnection *connection, + DBusResultCode error_code, + void *data); + +DBusConnection* dbus_connection_open (const char *address, + DBusResultCode *result); +void dbus_connection_ref (DBusConnection *connection); +void dbus_connection_unref (DBusConnection *connection); +void dbus_connection_disconnect (DBusConnection *connection); +dbus_bool_t dbus_connection_get_is_connected (DBusConnection *connection); +dbus_bool_t dbus_connection_send_message (DBusConnection *connection, + DBusMessage *message, + DBusResultCode *result); +void dbus_connection_flush (DBusConnection *connection); + +int dbus_connection_get_n_messages (DBusConnection *connection); +DBusMessage* dbus_connection_peek_message (DBusConnection *connection); +DBusMessage* dbus_connection_pop_message (DBusConnection *connection); + + +void dbus_connection_set_error_function (DBusConnection *connection, + DBusConnectionErrorFunction error_function, + void *data, + DBusFreeFunction free_data_function); +void dbus_connection_set_watch_functions (DBusConnection *connection, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function); +void dbus_connection_handle_watch (DBusConnection *connection, + DBusWatch *watch, + unsigned int condition); + + +int dbus_watch_get_fd (DBusWatch *watch); +unsigned int dbus_watch_get_flags (DBusWatch *watch); +void* dbus_watch_get_data (DBusWatch *watch); +void dbus_watch_set_data (DBusWatch *watch, + void *data, + DBusFreeFunction free_data_function); + + +DBUS_END_DECLS; + +#endif /* DBUS_CONNECTION_H */ diff --git a/dbus/dbus-errors.c b/dbus/dbus-errors.c new file mode 100644 index 00000000..1214c1f5 --- /dev/null +++ b/dbus/dbus-errors.c @@ -0,0 +1,109 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-errors.c Error reporting + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "dbus-errors.h" + +/** + * @defgroup DBusErrors Error reporting + * @ingroup DBus + * @brief Error reporting + * + * Types and functions related to reporting errors. + * + * + * In essence D-BUS error reporting works as follows: + * + * @code + * DBusResultCode result = DBUS_RESULT_SUCCESS; + * dbus_some_function (arg1, arg2, &result); + * if (result != DBUS_RESULT_SUCCESS) + * printf ("an error occurred\n"); + * @endcode + * + * @{ + */ + +/** + * Set a result code at a result code location, + * if code_address is not #NULL. + * + * @param code_address place to store the result code. + * @param code the result code itself. + */ +void +dbus_set_result (DBusResultCode *code_address, + DBusResultCode code) +{ + if (code_address) + *code_address = code; +} + +/** + * Returns a string describing the given result code. + * + * @param code the result code to describe. + * @returns a constant string describing the code. + */ +const char* +dbus_result_to_string (DBusResultCode code) +{ + /* This is a switch to the compiler will complain if we + * aren't handling some codes + */ + switch (code) + { + case DBUS_RESULT_SUCCESS: + return "Success"; + case DBUS_RESULT_FAILED: + return "Unknown error"; + case DBUS_RESULT_NO_MEMORY: + return "Not enough memory available"; + case DBUS_RESULT_IO_ERROR: + return "Error reading or writing data"; + case DBUS_RESULT_BAD_ADDRESS: + return "Could not parse address"; + case DBUS_RESULT_NOT_SUPPORTED: + return "Feature not supported"; + case DBUS_RESULT_LIMITS_EXCEEDED: + return "Resource limits exceeded"; + case DBUS_RESULT_ACCESS_DENIED: + return "Permission denied"; + case DBUS_RESULT_AUTH_FAILED: + return "Could not authenticate to server"; + case DBUS_RESULT_NO_SERVER: + return "No server"; + case DBUS_RESULT_TIMEOUT: + return "Connection timed out"; + case DBUS_RESULT_NO_NETWORK: + return "Network unavailable"; + case DBUS_RESULT_ADDRESS_IN_USE: + return "Address already in use"; + case DBUS_RESULT_DISCONNECTED: + return "Disconnected."; + + /* no default, it would break our compiler warnings */ + } + + return "Invalid error code"; +} + +/** @} */ diff --git a/dbus/dbus-errors.h b/dbus/dbus-errors.h new file mode 100644 index 00000000..d45e2f37 --- /dev/null +++ b/dbus/dbus-errors.h @@ -0,0 +1,62 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-errors.h Error reporting + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_ERROR_H +#define DBUS_ERROR_H + +#include + +DBUS_BEGIN_DECLS; + +typedef enum +{ + DBUS_RESULT_SUCCESS, /**< Operation was successful. */ + DBUS_RESULT_FAILED, /**< Operation failed for unspecified reason. */ + DBUS_RESULT_NO_MEMORY, /**< Operation failed for lack of memory. */ + DBUS_RESULT_IO_ERROR, /**< Operation failed because of an IO error, + * typically the other end closed the + * connection. + */ + DBUS_RESULT_BAD_ADDRESS, /**< Address was bad, could not be parsed. */ + DBUS_RESULT_NOT_SUPPORTED, /**< Feature is not supported. */ + DBUS_RESULT_LIMITS_EXCEEDED, /**< Some kernel resource limit exceeded. */ + DBUS_RESULT_ACCESS_DENIED, /**< Some sort of permissions/security problem. */ + DBUS_RESULT_AUTH_FAILED, /**< Could not authenticate. */ + DBUS_RESULT_NO_SERVER, /**< No one listening on the other end. */ + DBUS_RESULT_TIMEOUT, /**< Timed out trying to connect. */ + DBUS_RESULT_NO_NETWORK, /**< Can't find the network */ + DBUS_RESULT_ADDRESS_IN_USE, /**< Someone's already using the address */ + DBUS_RESULT_DISCONNECTED /**< No more connection. */ +} DBusResultCode; + +void dbus_set_result (DBusResultCode *code_address, + DBusResultCode code); +const char* dbus_result_to_string (DBusResultCode code); + + +DBUS_END_DECLS; + +#endif /* DBUS_ERROR_H */ diff --git a/dbus/dbus-internals.c b/dbus/dbus-internals.c index 238baa89..e90703e4 100644 --- a/dbus/dbus-internals.c +++ b/dbus/dbus-internals.c @@ -23,6 +23,11 @@ #include "dbus-internals.h" #include #include +#include +#include +#include +#include +#include /** * @defgroup DBusInternals D-BUS internal implementation details @@ -31,7 +36,7 @@ */ /** - * @defgroup DBusInternalsUtils Utilities + * @defgroup DBusInternalsUtils Utilities and portability * @ingroup DBusInternals * @brief Utility functions (_dbus_assert(), _dbus_warn(), etc.) * @{ @@ -82,7 +87,13 @@ * * @param integer the integer to stuff into a pointer. */ - +/** + * @def _DBUS_ZERO + * + * Sets all bits in an object to zero. + * + * @param object the object to be zeroed. + */ /** * @def _DBUS_INT_MIN * @@ -93,6 +104,25 @@ * * Maximum value of type "int" */ +/** + * @def _DBUS_MAX_SUN_PATH_LENGTH + * + * Maximum length of the path to a UNIX domain socket, + * sockaddr_un::sun_path member. POSIX requires that all systems + * support at least 100 bytes here, including the nul termination. + * We use 99 for the max value to allow for the nul. + * + * We could probably also do sizeof (addr.sun_path) + * but this way we are the same on all platforms + * which is probably a good idea. + */ + +/** + * @typedef DBusForeachFunction + * + * Used to iterate over each item in a collection, such as + * a DBusList. + */ /** * Prints a warning message to stderr. @@ -111,6 +141,134 @@ _dbus_warn (const char *format, va_end (args); } +/** + * Prints a warning message to stderr + * if the user has enabled verbose mode. + * + * @param format printf-style format string. + */ +void +_dbus_verbose (const char *format, + ...) +{ + va_list args; + static dbus_bool_t verbose = TRUE; + static dbus_bool_t initted = FALSE; + + if (!verbose) + return; + + if (!initted) + { + verbose = getenv ("DBUS_VERBOSE") != NULL; + initted = TRUE; + } + + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); +} + +/** + * A wrapper around strerror() because some platforms + * may be lame and not have strerror(). + * + * @param error_number errno. + * @returns error description. + */ +const char* +_dbus_strerror (int error_number) +{ + return strerror (error_number); +} + +/** + * Converts a UNIX errno into a DBusResultCode. + * + * @param error_number the errno. + * @returns the result code. + */ +DBusResultCode +_dbus_result_from_errno (int error_number) +{ + switch (error_number) + { + case 0: + return DBUS_RESULT_SUCCESS; + +#ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: + return DBUS_RESULT_NOT_SUPPORTED; +#endif +#ifdef EAFNOSUPPORT + case EAFNOSUPPORT: + return DBUS_RESULT_NOT_SUPPORTED; +#endif +#ifdef ENFILE + case ENFILE: + return DBUS_RESULT_LIMITS_EXCEEDED; /* kernel out of memory */ +#endif +#ifdef EMFILE + case EMFILE: + return DBUS_RESULT_LIMITS_EXCEEDED; +#endif +#ifdef EACCES + case EACCES: + return DBUS_RESULT_ACCESS_DENIED; +#endif +#ifdef EPERM + case EPERM: + return DBUS_RESULT_ACCESS_DENIED; +#endif +#ifdef ENOBUFS + case ENOBUFS: + return DBUS_RESULT_NO_MEMORY; +#endif +#ifdef ENOMEM + case ENOMEM: + return DBUS_RESULT_NO_MEMORY; +#endif +#ifdef EINVAL + case EINVAL: + return DBUS_RESULT_FAILED; +#endif +#ifdef EBADF + case EBADF: + return DBUS_RESULT_FAILED; +#endif +#ifdef EFAULT + case EFAULT: + return DBUS_RESULT_FAILED; +#endif +#ifdef ENOTSOCK + case ENOTSOCK: + return DBUS_RESULT_FAILED; +#endif +#ifdef EISCONN + case EISCONN: + return DBUS_RESULT_FAILED; +#endif +#ifdef ECONNREFUSED + case ECONNREFUSED: + return DBUS_RESULT_NO_SERVER; +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: + return DBUS_RESULT_TIMEOUT; +#endif +#ifdef ENETUNREACH + case ENETUNREACH: + return DBUS_RESULT_NO_NETWORK; +#endif +#ifdef EADDRINUSE + case EADDRINUSE: + return DBUS_RESULT_ADDRESS_IN_USE; +#endif + } + + return DBUS_RESULT_FAILED; +} + /** * Duplicates a string. Result must be freed with * dbus_free(). Returns #NULL if memory allocation fails. @@ -139,4 +297,38 @@ _dbus_strdup (const char *str) return copy; } +/** + * Sets a file descriptor to be nonblocking. + * + * @param fd the file descriptor. + * @param result address of result code. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_set_fd_nonblocking (int fd, + DBusResultCode *result) +{ + int val; + + val = fcntl (fd, F_GETFL, 0); + if (val < 0) + { + dbus_set_result (result, _dbus_result_from_errno (errno)); + _dbus_verbose ("Failed to get flags for fd %d: %s\n", fd, + _dbus_strerror (errno)); + return FALSE; + } + + if (fcntl (fd, F_SETFL, val | O_NONBLOCK) < 0) + { + dbus_set_result (result, _dbus_result_from_errno (errno)); + _dbus_verbose ("Failed to set fd %d nonblocking: %s\n", + fd, _dbus_strerror (errno)); + + return FALSE; + } + + return TRUE; +} + /** @} */ diff --git a/dbus/dbus-internals.h b/dbus/dbus-internals.h index 74bae96e..5d001397 100644 --- a/dbus/dbus-internals.h +++ b/dbus/dbus-internals.h @@ -20,6 +20,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ +#ifdef DBUS_INSIDE_DBUS_H +#error "You can't include dbus-internals.h in the public header dbus.h" +#endif #ifndef DBUS_INTERNALS_H #define DBUS_INTERNALS_H @@ -28,13 +31,20 @@ #include #include +#include #include /* for abort() */ #include /* just so it's there in every file */ DBUS_BEGIN_DECLS; -void _dbus_warn (const char *format, - ...); +void _dbus_warn (const char *format, + ...); +void _dbus_verbose (const char *format, + ...); + +const char* _dbus_strerror (int error_number); + +DBusResultCode _dbus_result_from_errno (int error_number); #define _dbus_assert(condition) \ do { \ @@ -58,10 +68,19 @@ do { #define _DBUS_POINTER_TO_INT(pointer) ((long)(pointer)) #define _DBUS_INT_TO_POINTER(integer) ((void*)((long)(integer))) +#define _DBUS_ZERO(object) (memset (&(object), '\0', sizeof ((object)))) + char* _dbus_strdup (const char *str); #define _DBUS_INT_MIN (-_DBUS_INT_MAX - 1) #define _DBUS_INT_MAX 2147483647 +#define _DBUS_MAX_SUN_PATH_LENGTH 99 + +typedef void (* DBusForeachFunction) (void *element, + void *data); + +dbus_bool_t _dbus_set_fd_nonblocking (int fd, + DBusResultCode *result); DBUS_END_DECLS; diff --git a/dbus/dbus-list.c b/dbus/dbus-list.c index 0b8e5a4e..31e5ae3f 100644 --- a/dbus/dbus-list.c +++ b/dbus/dbus-list.c @@ -521,6 +521,34 @@ _dbus_list_get_length (DBusList **list) return length; } +/** + * Calls the given function for each element in the list. The + * function is passed the list element as its first argument, and the + * given data as its second argument. + * + * @param list address of the head of the list. + * @param function function to call for each element. + * @param data extra data for the function. + * + */ +void +_dbus_list_foreach (DBusList **list, + DBusForeachFunction function, + void *data) +{ + DBusList *link; + + link = *list; + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (list, link); + + (* function) (link->data, data); + + link = next; + } +} + /** @} */ #ifdef DBUS_BUILD_TESTS diff --git a/dbus/dbus-list.h b/dbus/dbus-list.h index d314ad5a..8de40002 100644 --- a/dbus/dbus-list.h +++ b/dbus/dbus-list.h @@ -24,6 +24,7 @@ #ifndef DBUS_LIST_H #define DBUS_LIST_H +#include #include #include @@ -63,7 +64,9 @@ dbus_bool_t _dbus_list_copy (DBusList **list, DBusList **dest); int _dbus_list_get_length (DBusList **list); - +void _dbus_list_foreach (DBusList **list, + DBusForeachFunction function, + void *data); #define _dbus_list_get_next_link(list, link) ((link)->next == *(list) ? NULL : (link)->next) #define _dbus_list_get_prev_link(list, link) ((link)->prev == *(list) ? NULL : (link)->prev) diff --git a/dbus/dbus-message-internal.h b/dbus/dbus-message-internal.h new file mode 100644 index 00000000..5b1aea66 --- /dev/null +++ b/dbus/dbus-message-internal.h @@ -0,0 +1,56 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-message-internal.h DBusMessage object internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_MESSAGE_INTERNAL_H +#define DBUS_MESSAGE_INTERNAL_H + +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusMessageLoader DBusMessageLoader; + +void _dbus_message_get_network_data (DBusMessage *message, + const unsigned char **header, + int *header_len, + const unsigned char **body, + int *body_len); + +void _dbus_message_lock (DBusMessage *message); + + +DBusMessageLoader* _dbus_message_loader_new (void); +void _dbus_message_loader_ref (DBusMessageLoader *loader); +void _dbus_message_loader_unref (DBusMessageLoader *loader); +dbus_bool_t _dbus_message_loader_get_buffer (DBusMessageLoader *loader, + unsigned char **buffer, + int *buffer_len); +void _dbus_message_loader_return_buffer (DBusMessageLoader *loader, + unsigned char *buffer, + int bytes_read); + +DBusMessage* _dbus_message_loader_pop_message (DBusMessageLoader *loader); + + +DBUS_END_DECLS; + +#endif /* DBUS_MESSAGE_H */ diff --git a/dbus/dbus-message.c b/dbus/dbus-message.c index a52d7097..1e95bec7 100644 --- a/dbus/dbus-message.c +++ b/dbus/dbus-message.c @@ -21,7 +21,12 @@ * */ +#include "dbus-internals.h" #include "dbus-message.h" +#include "dbus-message-internal.h" +#include "dbus-memory.h" +#include "dbus-list.h" +#include /** * @defgroup DBusMessageInternals DBusMessage implementation details @@ -43,17 +48,72 @@ struct DBusMessage { int refcount; /**< Reference count */ - + + unsigned char *header; /**< Header network data, stored + * separately from body so we can + * independently realloc it. + */ + int header_len; /**< Length of header data. */ + + unsigned char *body; /**< Body network data. */ + int body_len; /**< Length of body data. */ + + unsigned int locked : 1; /**< Message being sent, no modifications allowed. */ }; +/** + * Gets the data to be sent over the network for this message. + * The header and then the body should be written out. + * This function is guaranteed to always return the same + * data once a message is locked (with _dbus_message_lock()). + * + * @param message the message. + * @param header return location for message header data. + * @param header_len return location for header length in bytes. + * @param body return location for message body data. + * @param body_len return location for body length in bytes. + */ +void +_dbus_message_get_network_data (DBusMessage *message, + const unsigned char **header, + int *header_len, + const unsigned char **body, + int *body_len) +{ + _dbus_assert (message->locked); + + *header = message->header; + *header_len = message->header_len; + *body = message->body; + *body_len = message->body_len; +} + +/** + * Locks a message. Allows checking that applications don't keep a + * reference to a message in the outgoing queue and change it + * underneath us. Messages are locked when they enter the outgoing + * queue (dbus_connection_send_message()), and the library complains + * if the message is modified while locked. + * + * @param message the message to lock. + */ +void +_dbus_message_lock (DBusMessage *message) +{ + message->locked = TRUE; +} + /** @} */ /** * @defgroup DBusMessage DBusMessage * @ingroup DBus - * @brief DBusMessage object + * @brief Message to be sent or received over a DBusConnection. * - * Types and functions related to the DBusMessage object. + * A DBusMessage is the most basic unit of communication over a + * DBusConnection. A DBusConnection represents a stream of messages + * received from a remote application, and a stream of messages + * sent to a remote application. * * @{ */ @@ -66,15 +126,30 @@ struct DBusMessage */ /** - * Constructs a new message. + * Constructs a new message. Returns #NULL if memory + * can't be allocated for the message. + * * @return a new DBusMessage, free with dbus_message_unref() * @see dbus_message_unref() */ DBusMessage* dbus_message_new (void) { + DBusMessage *message; + + message = dbus_new0 (DBusMessage, 1); + if (message == NULL) + return NULL; - return NULL; + message->refcount = 1; + + /* We need to decide what a message contains. ;-) */ + message->header = _dbus_strdup ("H"); + message->header_len = 2; + message->body = _dbus_strdup ("Body"); + message->body_len = 5; + + return message; } @@ -87,7 +162,7 @@ dbus_message_new (void) void dbus_message_ref (DBusMessage *message) { - + message->refcount += 1; } /** @@ -99,8 +174,246 @@ dbus_message_ref (DBusMessage *message) void dbus_message_unref (DBusMessage *message) { + _dbus_assert (message != NULL); + _dbus_assert (message->refcount > 0); + + message->refcount -= 1; + if (message->refcount == 0) + { + + dbus_free (message); + } +} + +/** @} */ + +/** + * @addtogroup DBusMessageInternals + * + * @{ + */ +/** + * @typedef DBusMessageLoader + * + * The DBusMessageLoader object encapsulates the process of converting + * a byte stream into a series of DBusMessage. It buffers the incoming + * bytes as efficiently as possible, and generates a queue of + * messages. DBusMessageLoader is typically used as part of a + * DBusTransport implementation. The DBusTransport then hands off + * the loaded messages to a DBusConnection, making the messages + * visible to the application. + * + */ + +/** + * Implementation details of DBusMessageLoader. + * All members are private. + */ +struct DBusMessageLoader +{ + int refcount; /**< Reference count. */ + + int allocated; /**< Allocated size of "data" */ + int length; /**< Used size of "data" */ + + unsigned char *data; /**< Buffered data. */ + + DBusList *messages; /**< Complete messages. */ + + unsigned int buffer_outstanding : 1; /**< Someone is using the buffer to read */ +}; + +/** + * The initial buffer size of the message loader. + * + * @todo this should be based on min header size plus some average + * body size, or something. Or rather, the min header size only, if we + * want to try to read only the header, store that in a DBusMessage, + * then read only the body and store that, etc., depends on + * how we optimize _dbus_message_loader_get_buffer() and what + * the exact message format is. + */ +#define INITIAL_LOADER_DATA_LEN 32 + +/** + * Creates a new message loader. Returns #NULL if memory can't + * be allocated. + * + * @returns new loader, or #NULL. + */ +DBusMessageLoader* +_dbus_message_loader_new (void) +{ + DBusMessageLoader *loader; + + loader = dbus_new0 (DBusMessageLoader, 1); + if (loader == NULL) + return NULL; + + loader->refcount = 1; + + /* Header, plus room for averagish other fields */ + loader->allocated = INITIAL_LOADER_DATA_LEN; + loader->data = dbus_malloc (loader->allocated); + if (loader->data == NULL) + loader->allocated = 0; + + loader->length = 0; + + return loader; +} + +/** + * Increments the reference count of the loader. + * + * @param loader the loader. + */ +void +_dbus_message_loader_ref (DBusMessageLoader *loader) +{ + loader->refcount += 1; +} + +/** + * Decrements the reference count of the loader and finalizes the + * loader when the count reaches zero. + * + * @param loader the loader. + */ +void +_dbus_message_loader_unref (DBusMessageLoader *loader) +{ + loader->refcount -= 1; + if (loader->refcount == 0) + { + _dbus_list_foreach (&loader->messages, + (DBusForeachFunction) dbus_message_unref, + NULL); + _dbus_list_clear (&loader->messages); + dbus_free (loader->data); + dbus_free (loader); + } +} + +/** + * Gets the buffer to use for reading data from the network. Network + * data is read directly into an allocated buffer, which is then used + * in the DBusMessage, to avoid as many extra memcpy's as possible. + * The buffer must always be returned immediately using + * _dbus_message_loader_return_buffer(), even if no bytes are + * successfully read. + * + * @todo this function can be a lot more clever. For example + * it can probably always return a buffer size to read exactly + * the body of the next message, thus avoiding any memory wastage + * or reallocs. + * + * @param loader the message loader. + * @param buffer address to store the buffer. + * @param buffer_len address to store the buffer length. + * @returns #FALSE if no buffer can be allocated. + */ +dbus_bool_t +_dbus_message_loader_get_buffer (DBusMessageLoader *loader, + unsigned char **buffer, + int *buffer_len) +{ + _dbus_assert (!loader->buffer_outstanding); + +#define MIN_BUFSIZE INITIAL_LOADER_DATA_LEN + + if ((loader->length + MIN_BUFSIZE) >= loader->allocated) + { + unsigned char *buf; + int new_allocated; + + /* double (and add MIN_BUFSIZE, in case allocated == 0) */ + new_allocated = MIN_BUFSIZE + loader->allocated * 2; + + if (new_allocated <= loader->allocated) + { + /* ugh, overflow. Maybe someone is trying to screw us. */ + /* (we could overflow so far that new_allocated > loader->allocated + * but nothing should break in that case) + */ + return FALSE; + } + + buf = dbus_realloc (loader->data, new_allocated); + if (buf == NULL) + return FALSE; + + loader->data = buf; + loader->allocated = new_allocated; + } + + *buffer = loader->data + loader->length; + *buffer_len = loader->allocated - loader->length; + + loader->buffer_outstanding = TRUE; + return TRUE; +} + +/** + * Returns a buffer obtained from _dbus_message_loader_get_buffer(), + * indicating to the loader how many bytes of the buffer were filled + * in. This function must always be called, even if no bytes were + * successfully read. + * + * @param loader the loader. + * @param buffer the buffer. + * @param bytes_read number of bytes that were read into the buffer. + */ +void +_dbus_message_loader_return_buffer (DBusMessageLoader *loader, + unsigned char *buffer, + int bytes_read) +{ + _dbus_assert (loader->buffer_outstanding); + /* FIXME fake implementation just creates a message for every 7 + * bytes. The real implementation will pass ownership of + * loader->data to new messages, to avoid memcpy. We can also + * smart-realloc loader->data to shrink it if it's too big, though + * _dbus_message_loader_get_buffer() could strategically arrange for + * that to usually not happen. + */ + + loader->length += bytes_read; + + loader->buffer_outstanding = FALSE; + + while (loader->length >= 7) + { + DBusMessage *message; + + message = dbus_message_new (); + if (message == NULL) + break; /* ugh, postpone this I guess. */ + + _dbus_list_append (&loader->messages, message); + + memmove (loader->data, loader->data + 7, + loader->length - 7); + loader->length -= 7; + + _dbus_verbose ("Loaded message %p\n", message); + } +} + +/** + * Pops a loaded message (passing ownership of the message + * to the caller). Returns #NULL if no messages have been + * loaded. + * + * @param loader the loader. + * @returns the next message, or #NULL if none. + */ +DBusMessage* +_dbus_message_loader_pop_message (DBusMessageLoader *loader) +{ + return _dbus_list_pop_first (&loader->messages); } /** @} */ diff --git a/dbus/dbus-message.h b/dbus/dbus-message.h index ce618388..69d74e9c 100644 --- a/dbus/dbus-message.h +++ b/dbus/dbus-message.h @@ -28,6 +28,7 @@ #define DBUS_MESSAGE_H #include +#include DBUS_BEGIN_DECLS; diff --git a/dbus/dbus-server-protected.h b/dbus/dbus-server-protected.h new file mode 100644 index 00000000..fb57c2ee --- /dev/null +++ b/dbus/dbus-server-protected.h @@ -0,0 +1,79 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-server-protected.h Used by subclasses of DBusServer object (internal to D-BUS implementation) + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_SERVER_PROTECTED_H +#define DBUS_SERVER_PROTECTED_H + +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusServerVTable DBusServerVTable; + +struct DBusServerVTable +{ + void (* finalize) (DBusServer *server); + /**< The finalize method must free the server. */ + + void (* handle_watch) (DBusServer *server, + DBusWatch *watch, + unsigned int flags); + /**< The handle_watch method handles reading/writing + * data as indicated by the flags. + */ + + void (* disconnect) (DBusServer *server); + /**< Disconnect this server. */ +}; + +struct DBusServer +{ + int refcount; /**< Reference count. */ + const DBusServerVTable *vtable; /**< Virtual methods for this instance. */ + DBusWatchList *watches; + + DBusNewConnectionFunction new_connection_function; + /**< Callback to invoke when a new connection is created. */ + void *new_connection_data; + /**< Data for new connection callback */ + DBusFreeFunction new_connection_free_data_function; + /**< Callback to invoke to free new_connection_data + * when server is finalized or data is replaced. + */ + + unsigned int disconnected : 1; /**< TRUE if we are disconnected. */ +}; + +dbus_bool_t _dbus_server_init_base (DBusServer *server, + const DBusServerVTable *vtable); +void _dbus_server_finalize_base (DBusServer *server); +dbus_bool_t _dbus_server_add_watch (DBusServer *server, + DBusWatch *watch); +void _dbus_server_remove_watch (DBusServer *server, + DBusWatch *watch); + + +DBUS_END_DECLS; + +#endif /* DBUS_SERVER_PROTECTED_H */ diff --git a/dbus/dbus-server-unix.c b/dbus/dbus-server-unix.c new file mode 100644 index 00000000..277e00a6 --- /dev/null +++ b/dbus/dbus-server-unix.c @@ -0,0 +1,303 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-server-unix.c Server implementation for Unix network protocols. + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dbus-internals.h" +#include "dbus-server-unix.h" +#include "dbus-transport-unix.h" +#include "dbus-connection-internal.h" +#include +#include +#include +#include +#include +#include + +/** + * @defgroup DBusServerUnix DBusServer implementations for UNIX + * @ingroup DBusInternals + * @brief Implementation details of DBusServer on UNIX + * + * @{ + */ +/** + * + * Opaque object representing a Unix server implementation. + */ +typedef struct DBusServerUnix DBusServerUnix; + +/** + * Implementation details of DBusServerUnix. All members + * are private. + */ +struct DBusServerUnix +{ + DBusServer base; /**< Parent class members. */ + int fd; /**< File descriptor or -1 if disconnected. */ + DBusWatch *watch; /**< File descriptor watch. */ +}; + +static void +unix_finalize (DBusServer *server) +{ + DBusServerUnix *unix_server = (DBusServerUnix*) server; + + _dbus_server_finalize_base (server); + + if (unix_server->watch) + _dbus_watch_unref (unix_server->watch); + + dbus_free (server); +} + +static void +handle_new_client_fd (DBusServer *server, + int client_fd) +{ + DBusConnection *connection; + DBusTransport *transport; + + _dbus_verbose ("Creating new client connection with fd %d\n", client_fd); + + if (!_dbus_set_fd_nonblocking (client_fd, NULL)) + return; + + transport = _dbus_transport_new_for_fd (client_fd); + if (transport == NULL) + { + close (client_fd); + return; + } + + /* note that client_fd is now owned by the transport, and will be + * closed on transport disconnection/finalization + */ + + connection = _dbus_connection_new_for_transport (transport); + _dbus_transport_unref (transport); + + if (connection == NULL) + return; + + /* See if someone wants to handle this new connection, + * self-referencing for paranoia + */ + if (server->new_connection_function) + { + dbus_server_ref (server); + + (* server->new_connection_function) (server, connection, + server->new_connection_data); + dbus_server_unref (server); + } + + /* If no one grabbed a reference, the connection will die. */ + dbus_connection_unref (connection); +} + +static void +unix_handle_watch (DBusServer *server, + DBusWatch *watch, + unsigned int flags) +{ + DBusServerUnix *unix_server = (DBusServerUnix*) server; + + _dbus_assert (watch == unix_server->watch); + + _dbus_verbose ("Handling client connection, flags 0x%x\n", flags); + + if (flags & DBUS_WATCH_READABLE) + { + int client_fd; + int listen_fd; + + listen_fd = dbus_watch_get_fd (watch); + + retry: + client_fd = accept (listen_fd, NULL, NULL); + + if (client_fd < 0) + { + if (errno == EINTR) + goto retry; + else if (errno == EAGAIN || errno == EWOULDBLOCK) + _dbus_verbose ("No client available to accept after all\n"); + else + _dbus_verbose ("Failed to accept a client connection: %s\n", + _dbus_strerror (errno)); + } + else + { + handle_new_client_fd (server, client_fd); + } + } + + if (flags & DBUS_WATCH_ERROR) + _dbus_verbose ("Error on server listening socket\n"); + + if (flags & DBUS_WATCH_HANGUP) + _dbus_verbose ("Hangup on server listening socket\n"); +} + +static void +unix_disconnect (DBusServer *server) +{ + DBusServerUnix *unix_server = (DBusServerUnix*) server; + + if (unix_server->watch) + { + _dbus_server_remove_watch (server, + unix_server->watch); + _dbus_watch_unref (unix_server->watch); + unix_server->watch = NULL; + } + + close (unix_server->fd); + unix_server->fd = -1; +} + +static DBusServerVTable unix_vtable = { + unix_finalize, + unix_handle_watch, + unix_disconnect +}; + +/** + * Creates a new server listening on the given file descriptor. The + * file descriptor should be nonblocking (use + * _dbus_set_fd_nonblocking() to make it so). The file descriptor + * should be listening for connections, that is, listen() should have + * been successfully invoked on it. The server will use accept() to + * accept new client connections. + * + * @param fd the file descriptor. + * @returns the new server, or #NULL if no memory. + * + */ +DBusServer* +_dbus_server_new_for_fd (int fd) +{ + DBusServerUnix *unix_server; + DBusWatch *watch; + + watch = _dbus_watch_new (fd, + DBUS_WATCH_READABLE); + if (watch == NULL) + return NULL; + + unix_server = dbus_new0 (DBusServerUnix, 1); + if (unix_server == NULL) + { + _dbus_watch_unref (watch); + return NULL; + } + + if (!_dbus_server_init_base (&unix_server->base, + &unix_vtable)) + { + _dbus_watch_unref (watch); + dbus_free (unix_server); + return NULL; + } + + if (!_dbus_server_add_watch (&unix_server->base, + watch)) + { + _dbus_server_finalize_base (&unix_server->base); + _dbus_watch_unref (watch); + dbus_free (unix_server); + return NULL; + } + + unix_server->fd = fd; + unix_server->watch = watch; + + return (DBusServer*) unix_server; +} + +/** + * Creates a new server listening on the given Unix domain socket. + * + * @param path the path for the domain socket. + * @param result location to store reason for failure. + * @returns the new server, or #NULL on failure. + */ +DBusServer* +_dbus_server_new_for_domain_socket (const char *path, + DBusResultCode *result) +{ + DBusServer *server; + int listen_fd; + struct sockaddr_un addr; + + listen_fd = socket (AF_LOCAL, SOCK_STREAM, 0); + + if (listen_fd < 0) + { + dbus_set_result (result, _dbus_result_from_errno (errno)); + _dbus_verbose ("Failed to create socket \"%s\": %s\n", + path, _dbus_strerror (errno)); + return NULL; + } + + if (!_dbus_set_fd_nonblocking (listen_fd, result)) + { + close (listen_fd); + return NULL; + } + + _DBUS_ZERO (addr); + addr.sun_family = AF_LOCAL; + strncpy (addr.sun_path, path, _DBUS_MAX_SUN_PATH_LENGTH); + addr.sun_path[_DBUS_MAX_SUN_PATH_LENGTH] = '\0'; + + if (bind (listen_fd, (struct sockaddr*) &addr, SUN_LEN (&addr)) < 0) + { + dbus_set_result (result, _dbus_result_from_errno (errno)); + _dbus_verbose ("Failed to bind socket \"%s\": %s\n", + path, _dbus_strerror (errno)); + close (listen_fd); + return NULL; + } + + if (listen (listen_fd, 30 /* backlog */) < 0) + { + dbus_set_result (result, _dbus_result_from_errno (errno)); + _dbus_verbose ("Failed to listen on socket \"%s\": %s\n", + path, _dbus_strerror (errno)); + close (listen_fd); + return NULL; + } + + server = _dbus_server_new_for_fd (listen_fd); + if (server == NULL) + { + dbus_set_result (result, DBUS_RESULT_NO_MEMORY); + close (listen_fd); + return NULL; + } + + return server; +} + +/** @} */ + diff --git a/dbus/dbus-server-unix.h b/dbus/dbus-server-unix.h new file mode 100644 index 00000000..43614cc0 --- /dev/null +++ b/dbus/dbus-server-unix.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-server-unix.h Server implementation for Unix network protocols. + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_SERVER_UNIX_H +#define DBUS_SERVER_UNIX_H + +#include +#include + +DBUS_BEGIN_DECLS; + +DBusServer* _dbus_server_new_for_fd (int fd); +DBusServer* _dbus_server_new_for_domain_socket (const char *path, + DBusResultCode *result); + +DBUS_END_DECLS; + +#endif /* DBUS_SERVER_UNIX_H */ diff --git a/dbus/dbus-server.c b/dbus/dbus-server.c new file mode 100644 index 00000000..c4b36bd6 --- /dev/null +++ b/dbus/dbus-server.c @@ -0,0 +1,299 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-server.c DBusServer object + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "dbus-server.h" +#include "dbus-server-unix.h" + +/** + * @defgroup DBusServer DBusServer + * @ingroup DBus + * @brief Server that listens for new connections. + * + * Types and functions related to DBusServer. + * A DBusServer represents a server that other applications + * can connect to. Each connection from another application + * is represented by a DBusConnection. + */ + +/** + * @defgroup DBusServerInternals DBusServer implementation details + * @ingroup DBusInternals + * @brief Implementation details of DBusServer + * + * @{ + */ + +/** + * Initializes the members of the DBusServer base class. + * Chained up to by subclass constructors. + * + * @param server the server. + * @param vtable the vtable for the subclass. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_server_init_base (DBusServer *server, + const DBusServerVTable *vtable) +{ + server->vtable = vtable; + server->refcount = 1; + + server->watches = _dbus_watch_list_new (); + if (server->watches == NULL) + return FALSE; + + return TRUE; +} + +/** + * Finalizes the members of the DBusServer base class. + * Chained up to by subclass finalizers. + * + * @param server the server. + */ +void +_dbus_server_finalize_base (DBusServer *server) +{ + dbus_server_set_new_connection_function (server, NULL, NULL, NULL); + + if (!server->disconnected) + dbus_server_disconnect (server); + + _dbus_watch_list_free (server->watches); +} + +/** + * Adds a watch for this server, chaining out to application-provided + * watch handlers. + * + * @param server the server. + * @param watch the watch to add. + */ +dbus_bool_t +_dbus_server_add_watch (DBusServer *server, + DBusWatch *watch) +{ + return _dbus_watch_list_add_watch (server->watches, watch); +} + +/** + * Removes a watch previously added with _dbus_server_remove_watch(). + * + * @param server the server. + * @param watch the watch to remove. + */ +void +_dbus_server_remove_watch (DBusServer *server, + DBusWatch *watch) +{ + _dbus_watch_list_remove_watch (server->watches, watch); +} + + +/** @} */ + +/** + * @addtogroup DBusServer + * + * @{ + */ + + +/** + * @typedef DBusServer + * + * An opaque object representing a server that listens for + * connections from other applications. Each time a connection + * is made, a new DBusConnection is created and made available + * via an application-provided DBusNewConnectionFunction. + * The DBusNewConnectionFunction is provided with + * dbus_server_set_new_connection_function(). + * + */ + +/** + * Listens for new connections on the given address. + * Returns #NULL if listening fails for any reason. + * Otherwise returns a new #DBusServer. + * dbus_server_set_new_connection_function() and + * dbus_server_set_watch_functions() should be called + * immediately to render the server fully functional. + * + * @param address the address of this server. + * @param result location to store rationale for failure. + * @returns a new DBusServer, or #NULL on failure. + * + */ +DBusServer* +dbus_server_listen (const char *address, + DBusResultCode *result) +{ + DBusServer *server; + + /* For now just pretend the address is a unix domain socket path */ + server = _dbus_server_new_for_domain_socket (address, result); + + return server; +} + +/** + * Increments the reference count of a DBusServer. + * + * @param server the server. + */ +void +dbus_server_ref (DBusServer *server) +{ + server->refcount += 1; +} + +/** + * Decrements the reference count of a DBusServer. Finalizes the + * server if the reference count reaches zero. The server connection + * will be closed as with dbus_server_disconnect() when the server is + * finalized. + * + * @param server the server. + */ +void +dbus_server_unref (DBusServer *server) +{ + _dbus_assert (server != NULL); + _dbus_assert (server->refcount > 0); + + server->refcount -= 1; + if (server->refcount == 0) + { + _dbus_assert (server->vtable->finalize != NULL); + + (* server->vtable->finalize) (server); + } +} + +/** + * Releases the server's address and stops listening for + * new clients. If called more than once, only the first + * call has an effect. Does not modify the server's + * reference count. + * + * @param server the server. + */ +void +dbus_server_disconnect (DBusServer *server) +{ + _dbus_assert (server->vtable->disconnect != NULL); + + if (server->disconnected) + return; + + (* server->vtable->disconnect) (server); + server->disconnected = TRUE; +} + +/** + * Returns #TRUE if the server is still listening for new connections. + * + * @param server the server. + */ +dbus_bool_t +dbus_server_get_is_connected (DBusServer *server) +{ + return !server->disconnected; +} + +/** + * Sets a function to be used for handling new connections. The given + * function is passed each new connection as the connection is + * created. If the new connection function increments the connection's + * reference count, the connection will stay alive. Otherwise, the + * connection will be unreferenced and closed. + * + * @param server the server. + * @param function a function to handle new connections. + * @param data data to pass to the new connection handler. + * @param free_data_function function to free the data. + */ +void +dbus_server_set_new_connection_function (DBusServer *server, + DBusNewConnectionFunction function, + void *data, + DBusFreeFunction free_data_function) +{ + if (server->new_connection_free_data_function != NULL) + (* server->new_connection_free_data_function) (server->new_connection_data); + + server->new_connection_function = function; + server->new_connection_data = data; + server->new_connection_free_data_function = free_data_function; +} + +/** + * Sets the watch functions for the connection. These functions are + * responsible for making the application's main loop aware of file + * descriptors that need to be monitored for events. + * + * This function behaves exactly like dbus_connection_set_watch_functions(); + * see the documentation for that routine. + * + * @param server the server. + * @param add_function function to begin monitoring a new descriptor. + * @param remove_function function to stop monitoring a descriptor. + * @param data data to pass to add_function and remove_function. + * @param free_data_function function to be called to free the data. + */ +void +dbus_server_set_watch_functions (DBusServer *server, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function) +{ + _dbus_watch_list_set_functions (server->watches, + add_function, + remove_function, + data, + free_data_function); +} + +/** + * Called to notify the server when a previously-added watch + * is ready for reading or writing, or has an exception such + * as a hangup. + * + * @param server the server. + * @param watch the watch. + * @param condition the current condition of the file descriptors being watched. + */ +void +dbus_server_handle_watch (DBusServer *server, + DBusWatch *watch, + unsigned int condition) +{ + _dbus_assert (server->vtable->handle_watch != NULL); + + _dbus_watch_sanitize_condition (watch, &condition); + + (* server->vtable->handle_watch) (server, watch, condition); +} + +/** @} */ + diff --git a/dbus/dbus-server.h b/dbus/dbus-server.h new file mode 100644 index 00000000..83f76100 --- /dev/null +++ b/dbus/dbus-server.h @@ -0,0 +1,66 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-server.h DBusServer object + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_SERVER_H +#define DBUS_SERVER_H + +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusServer DBusServer; + +typedef void (* DBusNewConnectionFunction) (DBusServer *server, + DBusConnection *new_connection, + void *data); + +DBusServer* dbus_server_listen (const char *address, + DBusResultCode *result); +void dbus_server_ref (DBusServer *server); +void dbus_server_unref (DBusServer *server); +void dbus_server_disconnect (DBusServer *server); +dbus_bool_t dbus_server_get_is_connected (DBusServer *server); + +void dbus_server_set_new_connection_function (DBusServer *server, + DBusNewConnectionFunction function, + void *data, + DBusFreeFunction free_data_function); +void dbus_server_set_watch_functions (DBusServer *server, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function); +void dbus_server_handle_watch (DBusServer *server, + DBusWatch *watch, + unsigned int condition); + + + +DBUS_END_DECLS; + +#endif /* DBUS_SERVER_H */ diff --git a/dbus/dbus-transport-protected.h b/dbus/dbus-transport-protected.h new file mode 100644 index 00000000..e6c696f0 --- /dev/null +++ b/dbus/dbus-transport-protected.h @@ -0,0 +1,88 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-transport-protected.h Used by subclasses of DBusTransport object (internal to D-BUS implementation) + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_TRANSPORT_PROTECTED_H +#define DBUS_TRANSPORT_PROTECTED_H + +#include +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusTransportVTable DBusTransportVTable; + +struct DBusTransportVTable +{ + void (* finalize) (DBusTransport *transport); + /**< The finalize method must free the transport. */ + + void (* handle_watch) (DBusTransport *transport, + DBusWatch *watch, + unsigned int flags); + /**< The handle_watch method handles reading/writing + * data as indicated by the flags. + */ + + void (* disconnect) (DBusTransport *transport); + /**< Disconnect this transport. */ + + void (* connection_set) (DBusTransport *transport); + /**< Called when transport->connection has been filled in */ + + void (* messages_pending) (DBusTransport *transport, + int queue_length); + /**< Called when the outgoing message queue goes from empty + * to non-empty or vice versa. + */ + + void (* do_iteration) (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds); + /**< Called to do a single "iteration" (block on select/poll + * followed by reading or writing data). + */ +}; + +struct DBusTransport +{ + int refcount; /**< Reference count. */ + + const DBusTransportVTable *vtable; /**< Virtual methods for this instance. */ + + DBusConnection *connection; /**< Connection owning this transport. */ + + DBusMessageLoader *loader; /**< Message-loading buffer. */ + + unsigned int disconnected : 1; /**< TRUE if we are disconnected. */ +}; + +dbus_bool_t _dbus_transport_init_base (DBusTransport *transport, + const DBusTransportVTable *vtable); +void _dbus_transport_finalize_base (DBusTransport *transport); + + + +DBUS_END_DECLS; + +#endif /* DBUS_TRANSPORT_PROTECTED_H */ diff --git a/dbus/dbus-transport-unix.c b/dbus/dbus-transport-unix.c new file mode 100644 index 00000000..869aa33f --- /dev/null +++ b/dbus/dbus-transport-unix.c @@ -0,0 +1,581 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-transport-unix.c UNIX socket subclasses of DBusTransport + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dbus-internals.h" +#include "dbus-connection-internal.h" +#include "dbus-transport-unix.h" +#include "dbus-transport-protected.h" +#include "dbus-watch.h" +#include +#include +#include +#include +#include +#include +#ifdef HAVE_WRITEV +#include +#endif + +/** + * @defgroup DBusTransportUnix DBusTransport implementations for UNIX + * @ingroup DBusInternals + * @brief Implementation details of DBusTransport on UNIX + * + * @{ + */ + +/** + * Opaque object representing a Unix file descriptor transport. + */ +typedef struct DBusTransportUnix DBusTransportUnix; + +/** + * Implementation details of DBusTransportUnix. All members are private. + */ +struct DBusTransportUnix +{ + DBusTransport base; /**< Parent instance */ + int fd; /**< File descriptor. */ + DBusWatch *watch; /**< Watch for readability. */ + DBusWatch *write_watch; /**< Watch for writability. */ + + int max_bytes_read_per_iteration; /**< To avoid blocking too long. */ + int max_bytes_written_per_iteration; /**< To avoid blocking too long. */ + + int message_bytes_written; /**< Number of bytes of current + * outgoing message that have + * been written. + */ +}; + +static void +unix_finalize (DBusTransport *transport) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + + _dbus_transport_finalize_base (transport); + + if (unix_transport->watch) + { + _dbus_watch_invalidate (unix_transport->watch); + _dbus_watch_unref (unix_transport->watch); + } + + dbus_free (transport); +} + +static void +do_io_error (DBusTransport *transport) +{ + _dbus_transport_disconnect (transport); + _dbus_connection_transport_error (transport->connection, + DBUS_RESULT_DISCONNECTED); +} + +static void +do_writing (DBusTransport *transport) +{ + int total; + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + + total = 0; + + again: + + while (_dbus_connection_have_messages_to_send (transport->connection)) + { + int bytes_written; + DBusMessage *message; + const unsigned char *header; + const unsigned char *body; + int header_len, body_len; + + if (total > unix_transport->max_bytes_written_per_iteration) + { + _dbus_verbose ("%d bytes exceeds %d bytes written per iteration, returning\n", + total, unix_transport->max_bytes_written_per_iteration); + goto out; + } + + message = _dbus_connection_get_message_to_send (transport->connection); + _dbus_assert (message != NULL); + _dbus_message_lock (message); + + _dbus_message_get_network_data (message, + &header, &header_len, + &body, &body_len); + + if (unix_transport->message_bytes_written < header_len) + { +#ifdef HAVE_WRITEV + struct iovec vectors[2]; + + vectors[0].iov_base = header + unix_transport->message_bytes_written; + vectors[0].iov_len = header_len - unix_transport->message_bytes_written; + vectors[1].iov_base = body; + vectors[1].iov_len = body_len; + + bytes_written = writev (unix_transport->fd, + vectors, _DBUS_N_ELEMENTS (vectors)); +#else + bytes_written = write (unix_transport->fd, + header + unix_transport->message_bytes_written, + header_len - unix_transport->message_bytes_written); +#endif + } + else + { + bytes_written = write (unix_transport->fd, + body + + (unix_transport->message_bytes_written - header_len), + body_len - + (unix_transport->message_bytes_written - body_len)); + } + + if (bytes_written < 0) + { + if (errno == EINTR) + goto again; + else if (errno == EAGAIN || + errno == EWOULDBLOCK) + goto out; + else + { + _dbus_verbose ("Error writing to message bus: %s\n", + _dbus_strerror (errno)); + do_io_error (transport); + goto out; + } + } + else + { + _dbus_verbose (" wrote %d bytes\n", bytes_written); + + total += bytes_written; + unix_transport->message_bytes_written += bytes_written; + + _dbus_assert (unix_transport->message_bytes_written <= + (header_len + body_len)); + + if (unix_transport->message_bytes_written == (header_len + body_len)) + { + _dbus_connection_message_sent (transport->connection, + message); + unix_transport->message_bytes_written = 0; + } + } + } + + out: + return; /* I think some C compilers require a statement after a label */ +} + +static void +do_reading (DBusTransport *transport) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + unsigned char *buffer; + int buffer_len; + int bytes_read; + int total; + + total = 0; + + again: + + if (total > unix_transport->max_bytes_read_per_iteration) + { + _dbus_verbose ("%d bytes exceeds %d bytes read per iteration, returning\n", + total, unix_transport->max_bytes_read_per_iteration); + goto out; + } + + if (!_dbus_message_loader_get_buffer (transport->loader, + &buffer, &buffer_len)) + goto out; /* no memory for a buffer */ + + bytes_read = read (unix_transport->fd, + buffer, buffer_len); + + _dbus_message_loader_return_buffer (transport->loader, + buffer, + bytes_read < 0 ? 0 : bytes_read); + + if (bytes_read < 0) + { + if (errno == EINTR) + goto again; + else if (errno == EAGAIN || + errno == EWOULDBLOCK) + goto out; + else + { + _dbus_verbose ("Error reading from message bus: %s\n", + _dbus_strerror (errno)); + do_io_error (transport); + goto out; + } + } + else if (bytes_read == 0) + { + _dbus_verbose ("Disconnected from message bus\n"); + do_io_error (transport); + goto out; + } + else + { + DBusMessage *message; + + _dbus_verbose (" read %d bytes\n", bytes_read); + + total += bytes_read; + + /* Queue any messages */ + while ((message = _dbus_message_loader_pop_message (transport->loader))) + { + _dbus_verbose ("queueing received message %p\n", message); + + _dbus_connection_queue_received_message (transport->connection, + message); + dbus_message_unref (message); + } + + /* Try reading more data until we get EAGAIN and return, or + * exceed max bytes per iteration. If in blocking mode of + * course we'll block instead of returning. + */ + goto again; + } + + out: + return; /* I think some C compilers require a statement after a label */ +} + +static void +unix_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int flags) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + + _dbus_assert (watch == unix_transport->watch || + watch == unix_transport->write_watch); + + if (flags & (DBUS_WATCH_HANGUP | DBUS_WATCH_ERROR)) + { + _dbus_transport_disconnect (transport); + _dbus_connection_transport_error (transport->connection, + DBUS_RESULT_DISCONNECTED); + return; + } + + if (watch == unix_transport->watch && + (flags & DBUS_WATCH_READABLE)) + do_reading (transport); + else if (watch == unix_transport->write_watch && + (flags & DBUS_WATCH_WRITABLE)) + do_writing (transport); +} + +static void +unix_disconnect (DBusTransport *transport) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + + if (unix_transport->watch) + { + _dbus_connection_remove_watch (transport->connection, + unix_transport->watch); + _dbus_watch_invalidate (unix_transport->watch); + _dbus_watch_unref (unix_transport->watch); + unix_transport->watch = NULL; + } + + close (unix_transport->fd); + unix_transport->fd = -1; +} + +static void +unix_connection_set (DBusTransport *transport) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + DBusWatch *watch; + + _dbus_assert (unix_transport->watch == NULL); + + watch = _dbus_watch_new (unix_transport->fd, + DBUS_WATCH_READABLE); + + if (watch == NULL) + { + _dbus_transport_disconnect (transport); + return; + } + + if (!_dbus_connection_add_watch (transport->connection, + watch)) + { + _dbus_transport_disconnect (transport); + return; + } + + unix_transport->watch = watch; +} + +static void +unix_messages_pending (DBusTransport *transport, + int messages_pending) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + + if (messages_pending > 0 && + unix_transport->write_watch == NULL) + { + unix_transport->write_watch = + _dbus_watch_new (unix_transport->fd, + DBUS_WATCH_WRITABLE); + + /* we can maybe add it some other time, just silently bomb */ + if (unix_transport->write_watch == NULL) + return; + + if (!_dbus_connection_add_watch (transport->connection, + unix_transport->write_watch)) + { + _dbus_watch_invalidate (unix_transport->write_watch); + _dbus_watch_unref (unix_transport->write_watch); + unix_transport->write_watch = NULL; + } + } + else if (messages_pending == 0 && + unix_transport->write_watch != NULL) + { + _dbus_connection_remove_watch (transport->connection, + unix_transport->write_watch); + _dbus_watch_invalidate (unix_transport->write_watch); + _dbus_watch_unref (unix_transport->write_watch); + unix_transport->write_watch = NULL; + } +} + +static void +unix_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds) +{ + DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport; + fd_set read_set; + fd_set write_set; + dbus_bool_t do_select; + + do_select = FALSE; + + FD_ZERO (&read_set); + if (flags & DBUS_ITERATION_DO_READING) + { + FD_SET (unix_transport->fd, &read_set); + do_select = TRUE; + } + + FD_ZERO (&write_set); + if (flags & DBUS_ITERATION_DO_WRITING) + { + FD_SET (unix_transport->fd, &write_set); + do_select = TRUE; + } + + if (do_select) + { + fd_set err_set; + struct timeval timeout; + dbus_bool_t use_timeout; + + again: + + FD_ZERO (&err_set); + FD_SET (unix_transport->fd, &err_set); + + if (flags & DBUS_ITERATION_BLOCK) + { + if (timeout_milliseconds >= 0) + { + timeout.tv_sec = timeout_milliseconds / 1000; + timeout.tv_usec = (timeout_milliseconds % 1000) * 1000; + + /* Always use timeout if one is passed in. */ + use_timeout = TRUE; + } + else + { + use_timeout = FALSE; /* NULL timeout to block forever */ + } + } + else + { + /* 0 timeout to not block */ + timeout.tv_sec = 0; + timeout.tv_usec = 0; + use_timeout = TRUE; + } + + if (select (unix_transport->fd + 1, &read_set, &write_set, &err_set, + use_timeout ? &timeout : NULL) >= 0) + { + if (FD_ISSET (unix_transport->fd, &err_set)) + do_io_error (transport); + else + { + if (FD_ISSET (unix_transport->fd, &read_set)) + do_reading (transport); + if (FD_ISSET (unix_transport->fd, &write_set)) + do_writing (transport); + } + } + else if (errno == EINTR) + goto again; + else + { + _dbus_verbose ("Error from select(): %s\n", + _dbus_strerror (errno)); + } + } +} + +static DBusTransportVTable unix_vtable = { + unix_finalize, + unix_handle_watch, + unix_disconnect, + unix_connection_set, + unix_messages_pending, + unix_do_iteration +}; + +/** + * Creates a new transport for the given file descriptor. The file + * descriptor must be nonblocking (use _dbus_set_fd_nonblocking() to + * make it so). This function is shared by various transports that + * boil down to a full duplex file descriptor. + * + * @param fd the file descriptor. + * @returns the new transport, or #NULL if no memory. + */ +DBusTransport* +_dbus_transport_new_for_fd (int fd) +{ + DBusTransportUnix *unix_transport; + + unix_transport = dbus_new0 (DBusTransportUnix, 1); + if (unix_transport == NULL) + return NULL; + + if (!_dbus_transport_init_base (&unix_transport->base, + &unix_vtable)) + { + dbus_free (unix_transport); + return NULL; + } + + unix_transport->fd = fd; + unix_transport->message_bytes_written = 0; + + /* These values should probably be tunable or something. */ + unix_transport->max_bytes_read_per_iteration = 2048; + unix_transport->max_bytes_written_per_iteration = 2048; + + return (DBusTransport*) unix_transport; +} + +/** + * Creates a new transport for the given Unix domain socket + * path. + * + * @param path the path to the domain socket. + * @param result location to store reason for failure. + * @returns a new transport, or #NULL on failure. + */ +DBusTransport* +_dbus_transport_new_for_domain_socket (const char *path, + DBusResultCode *result) +{ + int fd; + DBusTransport *transport; + struct sockaddr_un addr; + + transport = NULL; + + fd = socket (AF_LOCAL, SOCK_STREAM, 0); + + if (fd < 0) + { + dbus_set_result (result, + _dbus_result_from_errno (errno)); + + _dbus_verbose ("Failed to create socket: %s\n", + _dbus_strerror (errno)); + + goto out; + } + + _DBUS_ZERO (addr); + addr.sun_family = AF_LOCAL; + strncpy (addr.sun_path, path, _DBUS_MAX_SUN_PATH_LENGTH); + addr.sun_path[_DBUS_MAX_SUN_PATH_LENGTH] = '\0'; + + if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0) + { + dbus_set_result (result, + _dbus_result_from_errno (errno)); + + _dbus_verbose ("Failed to connect to socket %s: %s\n", + path, _dbus_strerror (errno)); + + close (fd); + fd = -1; + + goto out; + } + + if (!_dbus_set_fd_nonblocking (fd, result)) + { + close (fd); + fd = -1; + + goto out; + } + + transport = _dbus_transport_new_for_fd (fd); + if (transport == NULL) + { + dbus_set_result (result, DBUS_RESULT_NO_MEMORY); + close (fd); + fd = -1; + goto out; + } + + out: + return transport; +} + + +/** @} */ + diff --git a/dbus/dbus-transport-unix.h b/dbus/dbus-transport-unix.h new file mode 100644 index 00000000..c483f05e --- /dev/null +++ b/dbus/dbus-transport-unix.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-transport-unix.h UNIX socket subclasses of DBusTransport + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_TRANSPORT_UNIX_H +#define DBUS_TRANSPORT_UNIX_H + +#include + +DBUS_BEGIN_DECLS; + +DBusTransport* _dbus_transport_new_for_fd (int fd); +DBusTransport* _dbus_transport_new_for_domain_socket (const char *path, + DBusResultCode *result); + + +DBUS_END_DECLS; + +#endif /* DBUS_TRANSPORT_UNIX_H */ diff --git a/dbus/dbus-transport.c b/dbus/dbus-transport.c new file mode 100644 index 00000000..85498872 --- /dev/null +++ b/dbus/dbus-transport.c @@ -0,0 +1,288 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-transport.c DBusTransport object (internal to D-BUS implementation) + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dbus-transport-protected.h" +#include "dbus-transport-unix.h" +#include "dbus-connection-internal.h" + +/** + * @defgroup DBusTransport DBusTransport object + * @ingroup DBusInternals + * @brief "Backend" for a DBusConnection. + * + * Types and functions related to DBusTransport. A transport is an + * abstraction that can send and receive data via various kinds of + * network connections or other IPC mechanisms. + * + * @{ + */ + +/** + * @typedef DBusTransport + * + * Opaque object representing a way message stream. + * DBusTransport abstracts various kinds of actual + * transport mechanism, such as different network protocols, + * or encryption schemes. + */ + +/** + * Initializes the base class members of DBusTransport. + * Chained up to by subclasses in their constructor. + * + * @param transport the transport being created. + * @param vtable the subclass vtable. + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_transport_init_base (DBusTransport *transport, + const DBusTransportVTable *vtable) +{ + DBusMessageLoader *loader; + + loader = _dbus_message_loader_new (); + if (loader == NULL) + return FALSE; + + transport->refcount = 1; + transport->vtable = vtable; + transport->loader = loader; + + return TRUE; +} + +/** + * Finalizes base class members of DBusTransport. + * Chained up to from subclass finalizers. + * + * @param transport the transport. + */ +void +_dbus_transport_finalize_base (DBusTransport *transport) +{ + if (!transport->disconnected) + _dbus_transport_disconnect (transport); + + _dbus_message_loader_unref (transport->loader); +} + +/** + * Opens a new transport for the given address. + * + * @todo right now the address is just a Unix domain socket path. + * + * @param address the address. + * @param result location to store reason for failure. + * @returns new transport of #NULL on failure. + */ +DBusTransport* +_dbus_transport_open (const char *address, + DBusResultCode *result) +{ + DBusTransport *transport; + + /* FIXME parse the address - whatever format + * we decide addresses are in - and find the + * appropriate transport. + */ + + /* Pretend it's just a unix domain socket name for now */ + transport = _dbus_transport_new_for_domain_socket (address, result); + + return transport; +} + +/** + * Increments the reference count for the transport. + * + * @param transport the transport. + */ +void +_dbus_transport_ref (DBusTransport *transport) +{ + transport->refcount += 1; +} + +/** + * Decrements the reference count for the transport. + * Disconnects and finalizes the transport if + * the reference count reaches zero. + * + * @param transport the transport. + */ +void +_dbus_transport_unref (DBusTransport *transport) +{ + _dbus_assert (transport != NULL); + _dbus_assert (transport->refcount > 0); + + transport->refcount -= 1; + if (transport->refcount == 0) + { + _dbus_assert (transport->vtable->finalize != NULL); + + (* transport->vtable->finalize) (transport); + } +} + +/** + * Closes our end of the connection to a remote application. Further + * attempts to use this transport will fail. Only the first call to + * _dbus_transport_disconnect() will have an effect. + * + * @param transport the transport. + * + */ +void +_dbus_transport_disconnect (DBusTransport *transport) +{ + _dbus_assert (transport->vtable->disconnect != NULL); + + if (transport->disconnected) + return; + + (* transport->vtable->disconnect) (transport); + + transport->disconnected = TRUE; +} + +/** + * Returns #TRUE if the transport has not been disconnected. + * Disconnection can result from _dbus_transport_disconnect() + * or because the server drops its end of the connection. + * + * @param transport the transport. + */ +dbus_bool_t +_dbus_transport_get_is_connected (DBusTransport *transport) +{ + return !transport->disconnected; +} + +/** + * Handles a watch by reading data, writing data, or disconnecting + * the transport, as appropriate for the given condition. + * + * @param transport the transport. + * @param watch the watch. + * @param condition the current state of the watched file descriptor. + */ +void +_dbus_transport_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int condition) +{ + _dbus_assert (transport->vtable->handle_watch != NULL); + + if (transport->disconnected) + { + _dbus_connection_transport_error (transport->connection, + DBUS_RESULT_DISCONNECTED); + return; + } + + _dbus_watch_sanitize_condition (watch, &condition); + + (* transport->vtable->handle_watch) (transport, watch, condition); +} + +/** + * Sets the connection using this transport. Allows the transport + * to add watches to the connection, queue incoming messages, + * and pull outgoing messages. + * + * @param transport the transport. + * @param connection the connection. + */ +void +_dbus_transport_set_connection (DBusTransport *transport, + DBusConnection *connection) +{ + _dbus_assert (transport->vtable->connection_set != NULL); + _dbus_assert (transport->connection == NULL); + + transport->connection = connection; + + (* transport->vtable->connection_set) (transport); +} + +/** + * Notifies the transport when the outgoing message queue goes from + * empty to non-empty or vice versa. Typically causes the transport to + * add or remove its DBUS_WATCH_WRITABLE watch. + * + * @param transport the transport. + * @param queue_length the length of the outgoing message queue. + * + */ +void +_dbus_transport_messages_pending (DBusTransport *transport, + int queue_length) +{ + _dbus_assert (transport->vtable->messages_pending != NULL); + + if (transport->disconnected) + { + _dbus_connection_transport_error (transport->connection, + DBUS_RESULT_DISCONNECTED); + return; + } + + (* transport->vtable->messages_pending) (transport, + queue_length); +} + +/** + * Performs a single poll()/select() on the transport's file + * descriptors and then reads/writes data as appropriate, + * queueing incoming messages and sending outgoing messages. + * This is the backend for _dbus_connection_do_iteration(). + * See _dbus_connection_do_iteration() for full details. + * + * @param transport the transport. + * @param flags indicates whether to read or write, and whether to block. + * @param timeout_milliseconds if blocking, timeout or -1 for no timeout. + */ +void +_dbus_transport_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds) +{ + _dbus_assert (transport->vtable->do_iteration != NULL); + + if ((flags & (DBUS_ITERATION_DO_WRITING | + DBUS_ITERATION_DO_READING)) == 0) + return; /* Nothing to do */ + + if (transport->disconnected) + { + _dbus_connection_transport_error (transport->connection, + DBUS_RESULT_DISCONNECTED); + return; + } + + (* transport->vtable->do_iteration) (transport, flags, + timeout_milliseconds); +} + +/** @} */ diff --git a/dbus/dbus-transport.h b/dbus/dbus-transport.h new file mode 100644 index 00000000..84c1f8da --- /dev/null +++ b/dbus/dbus-transport.h @@ -0,0 +1,53 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-transport.h DBusTransport object (internal to D-BUS implementation) + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_TRANSPORT_H +#define DBUS_TRANSPORT_H + +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusTransport DBusTransport; + +DBusTransport* _dbus_transport_open (const char *address, + DBusResultCode *result); +void _dbus_transport_ref (DBusTransport *transport); +void _dbus_transport_unref (DBusTransport *transport); +void _dbus_transport_disconnect (DBusTransport *transport); +dbus_bool_t _dbus_transport_get_is_connected (DBusTransport *transport); +void _dbus_transport_handle_watch (DBusTransport *transport, + DBusWatch *watch, + unsigned int condition); +void _dbus_transport_set_connection (DBusTransport *transport, + DBusConnection *connection); +void _dbus_transport_messages_pending (DBusTransport *transport, + int queue_length); +void _dbus_transport_do_iteration (DBusTransport *transport, + unsigned int flags, + int timeout_milliseconds); + + +DBUS_END_DECLS; + +#endif /* DBUS_TRANSPORT_H */ diff --git a/dbus/dbus-types.h b/dbus/dbus-types.h index d12b4100..e55e0053 100644 --- a/dbus/dbus-types.h +++ b/dbus/dbus-types.h @@ -28,8 +28,39 @@ #define DBUS_TYPES_H typedef unsigned int dbus_bool_t; - typedef unsigned int dbus_uint32_t; typedef int dbus_int32_t; + +/* Normally docs are in .c files, but there isn't a .c file for this. */ +/** + * @defgroup DBusTypes Basic types + * @ingroup DBus + * @brief dbus_bool_t, dbus_int32_t, etc. + * + * Typedefs for common primitive types. + * + * @{ + */ + +/** + * @typedef dbus_bool_t + * + * A boolean, valid values are #TRUE and #FALSE. + */ + +/** + * @typedef dbus_uint32_t + * + * A 32-bit unsigned integer on all platforms. + */ + +/** + * @typedef dbus_int32_t + * + * A 32-bit signed integer on all platforms. + */ + +/** @} */ + #endif /* DBUS_TYPES_H */ diff --git a/dbus/dbus-watch.c b/dbus/dbus-watch.c new file mode 100644 index 00000000..8f3c16bc --- /dev/null +++ b/dbus/dbus-watch.c @@ -0,0 +1,381 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-watch.c DBusWatch implementation + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dbus-internals.h" +#include "dbus-watch.h" +#include "dbus-list.h" + +/** + * @defgroup DBusWatchInternals DBusWatch implementation details + * @ingroup DBusInternals + * @brief implementation details for DBusWatch + * + * @{ + */ + +struct DBusWatch +{ + int refcount; /**< Reference count */ + int fd; /**< File descriptor. */ + unsigned int flags; /**< Conditions to watch. */ + void *data; /**< Application data. */ + DBusFreeFunction free_data_function; /**< Free the application data. */ +}; + +/** + * Creates a new DBusWatch. Normally used by a DBusTransport + * implementation. + * @param fd the file descriptor to be watched. + * @param flags the conditions to watch for on the descriptor. + * @returns the new DBusWatch object. + */ +DBusWatch* +_dbus_watch_new (int fd, + unsigned int flags) +{ + DBusWatch *watch; + +#define VALID_WATCH_FLAGS (DBUS_WATCH_WRITABLE | DBUS_WATCH_READABLE) + + _dbus_assert ((flags & VALID_WATCH_FLAGS) == flags); + + watch = dbus_new0 (DBusWatch, 1); + watch->refcount = 1; + watch->fd = fd; + watch->flags = flags; + + return watch; +} + +/** + * Increments the reference count of a DBusWatch object. + * + * @param watch the watch object. + */ +void +_dbus_watch_ref (DBusWatch *watch) +{ + watch->refcount += 1; +} + +/** + * Decrements the reference count of a DBusWatch object + * and finalizes the object if the count reaches zero. + * + * @param watch the watch object. + */ +void +_dbus_watch_unref (DBusWatch *watch) +{ + _dbus_assert (watch != NULL); + _dbus_assert (watch->refcount > 0); + + watch->refcount -= 1; + if (watch->refcount == 0) + { + dbus_watch_set_data (watch, NULL, NULL); /* call free_data_function */ + dbus_free (watch); + } +} + +/** + * Clears the file descriptor from a now-invalid watch object so that + * no one tries to use it. This is because a watch may stay alive due + * to reference counts after the file descriptor is closed. + * Invalidation makes it easier to catch bugs. It also + * keeps people from doing dorky things like assuming file descriptors + * are unique (never recycled). + * + * @param watch the watch object. + */ +void +_dbus_watch_invalidate (DBusWatch *watch) +{ + watch->fd = -1; + watch->flags = 0; +} + +/** + * Sanitizes the given condition so that it only contains + * flags that the DBusWatch requested. e.g. if the + * watch is a DBUS_WATCH_READABLE watch then + * DBUS_WATCH_WRITABLE will be stripped from the condition. + * + * @param watch the watch object. + * @param condition address of the condition to sanitize. + */ +void +_dbus_watch_sanitize_condition (DBusWatch *watch, + unsigned int *condition) +{ + if (!(watch->flags & DBUS_WATCH_READABLE)) + *condition &= ~DBUS_WATCH_READABLE; + if (!(watch->flags & DBUS_WATCH_WRITABLE)) + *condition &= ~DBUS_WATCH_WRITABLE; +} + + +/** + * @typedef DBusWatchList + * + * Opaque data type representing a list of watches + * and a set of DBusAddWatchFunction/DBusRemoveWatchFunction. + * Automatically handles removing/re-adding watches + * when the DBusAddWatchFunction is updated or changed. + * Holds a reference count to each watch. + * + * Used in the implementation of both DBusServer and + * DBusClient. + * + */ + +/** + * DBusWatchList implementation details. All fields + * are private. + * + */ +struct DBusWatchList +{ + DBusList *watches; /**< Watch objects. */ + + DBusAddWatchFunction add_watch_function; /**< Callback for adding a watch. */ + DBusAddWatchFunction remove_watch_function; /**< Callback for removing a watch. */ + void *watch_data; /**< Data for watch callbacks */ + DBusFreeFunction watch_free_data_function; /**< Free function for watch callback data */ +}; + +/** + * Creates a new watch list. Returns @NULL if insufficient + * memory exists. + * + * @returns the new watch list, or #NULL on failure. + */ +DBusWatchList* +_dbus_watch_list_new (void) +{ + DBusWatchList *watch_list; + + watch_list = dbus_new0 (DBusWatchList, 1); + if (watch_list == NULL) + return NULL; + + return watch_list; +} + +/** + * Frees a DBusWatchList. + * + * @param watch_list the watch list. + */ +void +_dbus_watch_list_free (DBusWatchList *watch_list) +{ + /* free watch_data as a side effect */ + _dbus_watch_list_set_functions (watch_list, + NULL, NULL, NULL, NULL); + + _dbus_list_foreach (&watch_list->watches, + (DBusForeachFunction) _dbus_watch_unref, + NULL); + _dbus_list_clear (&watch_list->watches); + + dbus_free (watch_list); +} + +/** + * Sets the watch functions. This function is the "backend" + * for dbus_connection_set_watch_functions() and + * dbus_server_set_watch_functions(). + * + * @param watch_list the watch list. + * @param add_function the add watch function. + * @param remove_function the remove watch function. + * @param data the data for those functions. + * @param free_data_function the function to free the data. + * + */ +void +_dbus_watch_list_set_functions (DBusWatchList *watch_list, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function) +{ + /* Remove all current watches from previous watch handlers */ + + if (watch_list->remove_watch_function != NULL) + { + _dbus_list_foreach (&watch_list->watches, + (DBusForeachFunction) watch_list->remove_watch_function, + watch_list->watch_data); + } + + if (watch_list->watch_free_data_function != NULL) + (* watch_list->watch_free_data_function) (watch_list->watch_data); + + watch_list->add_watch_function = add_function; + watch_list->remove_watch_function = remove_function; + watch_list->watch_data = data; + watch_list->watch_free_data_function = free_data_function; + + /* Re-add all pending watches */ + if (watch_list->add_watch_function != NULL) + { + _dbus_list_foreach (&watch_list->watches, + (DBusForeachFunction) watch_list->add_watch_function, + watch_list->watch_data); + } +} + +/** + * Adds a new watch to the watch list, invoking the + * application DBusAddWatchFunction if appropriate. + * + * @param watch_list the watch list. + * @param watch the watch to add. + * @returns #TRUE on success, #FALSE if no memory. + */ +dbus_bool_t +_dbus_watch_list_add_watch (DBusWatchList *watch_list, + DBusWatch *watch) +{ + if (!_dbus_list_append (&watch_list->watches, watch)) + return FALSE; + + _dbus_watch_ref (watch); + + if (watch_list->add_watch_function != NULL) + (* watch_list->add_watch_function) (watch, + watch_list->watch_data); + + return TRUE; +} + +/** + * Removes a watch from the watch list, invoking the + * application's DBusRemoveWatchFunction if appropriate. + * + * @param watch_list the watch list. + * @param watch the watch to remove. + */ +void +_dbus_watch_list_remove_watch (DBusWatchList *watch_list, + DBusWatch *watch) +{ + if (!_dbus_list_remove (&watch_list->watches, watch)) + _dbus_assert_not_reached ("Nonexistent watch was removed"); + + if (watch_list->remove_watch_function != NULL) + (* watch_list->remove_watch_function) (watch, + watch_list->watch_data); + + _dbus_watch_unref (watch); +} + +/** @} */ + +/** + * @defgroup DBusWatch DBusWatch + * @ingroup DBus + * @brief Object representing an file descriptor to be watched. + * + * Types and functions related to DBusWatch. A watch represents + * a file descriptor that the main loop needs to monitor, + * as in Qt's QSocketNotifier or GLib's g_io_add_watch(). + * + * @{ + */ + +/** + * @typedef DBusWatch + * + * Opaque object representing a file descriptor + * to be watched for changes in readability, + * writability, or hangup. + */ + +/** + * Gets the file descriptor that should be watched. + * + * @param watch the DBusWatch object. + * @returns the file descriptor to watch. + */ +int +dbus_watch_get_fd (DBusWatch *watch) +{ + return watch->fd; +} + +/** + * Gets flags from DBusWatchFlags indicating + * what conditions should be monitored on the + * file descriptor. + * + * @param watch the DBusWatch object. + * @returns the conditions to watch. + */ +unsigned int +dbus_watch_get_flags (DBusWatch *watch) +{ + _dbus_assert ((watch->flags & VALID_WATCH_FLAGS) == watch->flags); + + return watch->flags; +} + +/** + * Gets data previously set with dbus_watch_set_data() + * or #NULL if none. + * + * @param watch the DBusWatch object. + * @returns previously-set data. + */ +void* +dbus_watch_get_data (DBusWatch *watch) +{ + return watch->data; +} + +/** + * Sets data which can be retrieved with dbus_watch_get_data(). + * Intended for use by the DBusAddWatchFunction and + * DBusRemoveWatchFunction to store their own data. For example with + * Qt you might store the QSocketNotifier for this watch and with GLib + * you might store a GSource. + * + * @param watch the DBusWatch object. + * @param data the data. + * @param free_data_function function to be called to free the data. + */ +void +dbus_watch_set_data (DBusWatch *watch, + void *data, + DBusFreeFunction free_data_function) +{ + if (watch->free_data_function != NULL) + (* watch->free_data_function) (watch->data); + + watch->data = data; + watch->free_data_function = free_data_function; +} + +/** @} */ diff --git a/dbus/dbus-watch.h b/dbus/dbus-watch.h new file mode 100644 index 00000000..869605ae --- /dev/null +++ b/dbus/dbus-watch.h @@ -0,0 +1,60 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-watch.h DBusWatch internal interfaces + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#ifndef DBUS_WATCH_H +#define DBUS_WATCH_H + +#include +#include + +DBUS_BEGIN_DECLS; + +/* Public methods on DBusWatch are in dbus-connection.h */ + +typedef struct DBusWatchList DBusWatchList; + +DBusWatch* _dbus_watch_new (int fd, + unsigned int flags); +void _dbus_watch_ref (DBusWatch *watch); +void _dbus_watch_unref (DBusWatch *watch); +void _dbus_watch_invalidate (DBusWatch *watch); + +void _dbus_watch_sanitize_condition (DBusWatch *watch, + unsigned int *condition); + +DBusWatchList* _dbus_watch_list_new (void); +void _dbus_watch_list_free (DBusWatchList *watch_list); +void _dbus_watch_list_set_functions (DBusWatchList *watch_list, + DBusAddWatchFunction add_function, + DBusRemoveWatchFunction remove_function, + void *data, + DBusFreeFunction free_data_function); +dbus_bool_t _dbus_watch_list_add_watch (DBusWatchList *watch_list, + DBusWatch *watch); +void _dbus_watch_list_remove_watch (DBusWatchList *watch_list, + DBusWatch *watch); + + + +DBUS_END_DECLS; + +#endif /* DBUS_WATCH_H */ diff --git a/dbus/dbus.h b/dbus/dbus.h index c03eb311..8bebaf59 100644 --- a/dbus/dbus.h +++ b/dbus/dbus.h @@ -26,8 +26,11 @@ #define DBUS_INSIDE_DBUS_H 1 +#include +#include #include #include +#include #include #undef DBUS_INSIDE_DBUS_H @@ -42,4 +45,4 @@ /** @} */ -#endif /* DBUS_H +#endif /* DBUS_H */ diff --git a/doc/.cvsignore b/doc/.cvsignore new file mode 100644 index 00000000..444a9eba --- /dev/null +++ b/doc/.cvsignore @@ -0,0 +1,8 @@ +.deps +.libs +Makefile +Makefile.in +*.lo +*.la +*.o +api \ No newline at end of file diff --git a/test/.cvsignore b/test/.cvsignore new file mode 100644 index 00000000..53012e2b --- /dev/null +++ b/test/.cvsignore @@ -0,0 +1,9 @@ +.deps +.libs +Makefile +Makefile.in +*.lo +*.la +*.o +echo-server +echo-client diff --git a/test/Makefile.am b/test/Makefile.am index 6e42b47b..8f7d8e1d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,4 +1,23 @@ if DBUS_BUILD_TESTS +INCLUDES=$(DBUS_TEST_CFLAGS) + +noinst_PROGRAMS= echo-client echo-server + +echo_client_SOURCES= \ + echo-client.c \ + watch.c \ + watch.h + +echo_server_SOURCES= \ + echo-server.c \ + watch.c \ + watch.h + +TEST_LIBS=$(DBUS_TEST_LIBS) $(top_builddir)/dbus/libdbus-convenience.la $(top_builddir)/dbus/libdbus-1.la + +echo_client_LDADD=$(TEST_LIBS) +echo_server_LDADD=$(TEST_LIBS) + endif diff --git a/test/echo-client.c b/test/echo-client.c new file mode 100644 index 00000000..5c6ee425 --- /dev/null +++ b/test/echo-client.c @@ -0,0 +1,41 @@ +#include +#include +#include "watch.h" + +int +main (int argc, + char **argv) +{ + DBusConnection *connection; + DBusResultCode result; + DBusMessage *message; + + if (argc < 2) + { + fprintf (stderr, "Give the server address as an argument\n"); + return 1; + } + + connection = dbus_connection_open (argv[1], &result); + if (connection == NULL) + { + fprintf (stderr, "Failed to open connection to %s: %s\n", + argv[1], dbus_result_to_string (result)); + return 1; + } + + setup_connection (connection); + + /* Send a message to get things going */ + message = dbus_message_new (); + dbus_connection_send_message (connection, + message, + NULL); + dbus_message_unref (message); + + do_mainloop (); + + dbus_connection_unref (connection); + + return 0; +} diff --git a/test/echo-server.c b/test/echo-server.c new file mode 100644 index 00000000..99f97ffd --- /dev/null +++ b/test/echo-server.c @@ -0,0 +1,48 @@ +#include +#include +#include "watch.h" + +static void +new_connection_callback (DBusServer *server, + DBusConnection *new_connection, + void *data) +{ + printf ("Got new connection\n"); + + setup_connection (new_connection); +} + +int +main (int argc, + char **argv) +{ + DBusServer *server; + DBusResultCode result; + + if (argc < 2) + { + fprintf (stderr, "Give the server address as an argument\n"); + return 1; + } + + server = dbus_server_listen (argv[1], &result); + if (server == NULL) + { + fprintf (stderr, "Failed to start server on %s: %s\n", + argv[1], dbus_result_to_string (result)); + return 1; + } + + setup_server (server); + + dbus_server_set_new_connection_function (server, + new_connection_callback, + NULL, NULL); + + do_mainloop (); + + dbus_server_disconnect (server); + dbus_server_unref (server); + + return 0; +} diff --git a/test/watch.c b/test/watch.c new file mode 100644 index 00000000..df26855c --- /dev/null +++ b/test/watch.c @@ -0,0 +1,268 @@ +#include "watch.h" +#include + +#define DBUS_COMPILATION /* cheat and use DBusList */ +#include +#undef DBUS_COMPILATION + +/* Cheesy main loop used in test programs. Any real app would use the + * GLib or Qt or other non-sucky main loops. + */ + +#undef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +static DBusList *watches = NULL; +static dbus_bool_t exited = FALSE; +static DBusList *connections = NULL; + +typedef enum +{ + WATCH_CONNECTION, + WATCH_SERVER +} WatchType; + +typedef struct +{ + WatchType type; + void *data; +} WatchData; + +static void +add_connection_watch (DBusWatch *watch, + DBusConnection *connection) +{ + WatchData *wd; + + wd = dbus_new0 (WatchData, 1); + wd->type = WATCH_CONNECTION; + wd->data = connection; + + _dbus_list_append (&watches, watch); + dbus_watch_set_data (watch, wd, dbus_free); +} + +static void +remove_connection_watch (DBusWatch *watch, + DBusConnection *connection) +{ + _dbus_list_remove (&watches, watch); + dbus_watch_set_data (watch, NULL, NULL); +} + +static void +add_server_watch (DBusWatch *watch, + DBusServer *server) +{ + WatchData *wd; + + wd = dbus_new0 (WatchData, 1); + wd->type = WATCH_SERVER; + wd->data = server; + + _dbus_list_append (&watches, watch); + + dbus_watch_set_data (watch, wd, dbus_free); +} + +static void +remove_server_watch (DBusWatch *watch, + DBusServer *server) +{ + _dbus_list_remove (&watches, watch); + dbus_watch_set_data (watch, NULL, NULL); +} + +static int count = 0; + +static void +check_messages (void) +{ + DBusList *link; + + link = _dbus_list_get_first_link (&connections); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&connections, link); + DBusConnection *connection = link->data; + DBusMessage *message; + + while ((message = dbus_connection_pop_message (connection))) + { + DBusMessage *reply; + + printf ("Received message %d, sending reply\n", count); + + reply = dbus_message_new (); + dbus_connection_send_message (connection, + reply, + NULL); + dbus_message_unref (reply); + + dbus_message_unref (message); + + count += 1; + if (count > 100) + { + printf ("Saw %d messages, exiting\n", count); + quit_mainloop (); + } + } + + link = next; + } +} + +void +do_mainloop (void) +{ + /* Of course with any real app you'd use GMainLoop or + * QSocketNotifier and not have to see all this crap. + */ + + while (!exited && watches != NULL) + { + fd_set read_set; + fd_set write_set; + fd_set err_set; + int max_fd; + DBusList *link; + + check_messages (); + + FD_ZERO (&read_set); + FD_ZERO (&write_set); + FD_ZERO (&err_set); + + max_fd = -1; + + link = _dbus_list_get_first_link (&watches); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&watches, link); + int fd; + DBusWatch *watch; + unsigned int flags; + + watch = link->data; + + fd = dbus_watch_get_fd (watch); + flags = dbus_watch_get_flags (watch); + + max_fd = MAX (max_fd, fd); + + if (flags & DBUS_WATCH_READABLE) + FD_SET (fd, &read_set); + + if (flags & DBUS_WATCH_WRITABLE) + FD_SET (fd, &write_set); + + FD_SET (fd, &err_set); + + link = next; + } + + select (max_fd + 1, &read_set, &write_set, &err_set, NULL); + + link = _dbus_list_get_first_link (&watches); + while (link != NULL) + { + DBusList *next = _dbus_list_get_next_link (&watches, link); + int fd; + DBusWatch *watch; + unsigned int flags; + unsigned int condition; + + watch = link->data; + + fd = dbus_watch_get_fd (watch); + flags = dbus_watch_get_flags (watch); + + condition = 0; + + if ((flags & DBUS_WATCH_READABLE) && + FD_ISSET (fd, &read_set)) + condition |= DBUS_WATCH_READABLE; + + if ((flags & DBUS_WATCH_WRITABLE) && + FD_ISSET (fd, &write_set)) + condition |= DBUS_WATCH_WRITABLE; + + if (FD_ISSET (fd, &err_set)) + condition |= DBUS_WATCH_ERROR; + + if (condition != 0) + { + WatchData *wd; + + wd = dbus_watch_get_data (watch); + + if (wd->type == WATCH_CONNECTION) + { + DBusConnection *connection = wd->data; + + dbus_connection_handle_watch (connection, + watch, + condition); + } + else if (wd->type == WATCH_SERVER) + { + DBusServer *server = wd->data; + + dbus_server_handle_watch (server, + watch, + condition); + } + } + + link = next; + } + } +} + +void +quit_mainloop (void) +{ + exited = TRUE; +} + +static void +error_handler (DBusConnection *connection, + DBusResultCode error_code, + void *data) +{ + fprintf (stderr, + "Error on connection: %s\n", + dbus_result_to_string (error_code)); + + _dbus_list_remove (&connections, connection); + dbus_connection_unref (connection); + quit_mainloop (); +} + +void +setup_connection (DBusConnection *connection) +{ + dbus_connection_set_watch_functions (connection, + (DBusAddWatchFunction) add_connection_watch, + (DBusRemoveWatchFunction) remove_connection_watch, + connection, + NULL); + + dbus_connection_set_error_function (connection, + error_handler, + NULL, NULL); + + dbus_connection_ref (connection); + _dbus_list_append (&connections, connection); +} + +void +setup_server (DBusServer *server) +{ + dbus_server_set_watch_functions (server, + (DBusAddWatchFunction) add_server_watch, + (DBusRemoveWatchFunction) remove_server_watch, + server, + NULL); +} diff --git a/test/watch.h b/test/watch.h new file mode 100644 index 00000000..a9ad0834 --- /dev/null +++ b/test/watch.h @@ -0,0 +1,15 @@ +/* Cheesy main loop thingy used by the test programs */ + +#ifndef WATCH_H +#define WATCH_H + +#include + +void do_mainloop (void); + +void quit_mainloop (void); + +void setup_connection (DBusConnection *connection); +void setup_server (DBusServer *server); + +#endif -- cgit