summaryrefslogtreecommitdiffstats
path: root/dbus/dbus-transport-unix.c
diff options
context:
space:
mode:
Diffstat (limited to 'dbus/dbus-transport-unix.c')
-rw-r--r--dbus/dbus-transport-unix.c581
1 files changed, 581 insertions, 0 deletions
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 <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_WRITEV
+#include <sys/uio.h>
+#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;
+}
+
+
+/** @} */
+