| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* dbus-transport-socket.c Socket subclasses of DBusTransport |
| * |
| * Copyright (C) 2002, 2003, 2004, 2006 Red Hat Inc. |
| * |
| * Licensed under the Academic Free License version 2.1 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <config.h> |
| #include "dbus-internals.h" |
| #include "dbus-connection-internal.h" |
| #include "dbus-nonce.h" |
| #include "dbus-transport-socket.h" |
| #include "dbus-transport-protected.h" |
| #include "dbus-watch.h" |
| #include "dbus-credentials.h" |
| |
| /** |
| * @defgroup DBusTransportSocket DBusTransport implementations for sockets |
| * @ingroup DBusInternals |
| * @brief Implementation details of DBusTransport on sockets |
| * |
| * @{ |
| */ |
| |
| /** |
| * Opaque object representing a socket file descriptor transport. |
| */ |
| typedef struct DBusTransportSocket DBusTransportSocket; |
| |
| /** |
| * Implementation details of DBusTransportSocket. All members are private. |
| */ |
| struct DBusTransportSocket |
| { |
| DBusTransport base; /**< Parent instance */ |
| int fd; /**< File descriptor. */ |
| DBusWatch *read_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. |
| */ |
| DBusString encoded_outgoing; /**< Encoded version of current |
| * outgoing message. |
| */ |
| DBusString encoded_incoming; /**< Encoded version of current |
| * incoming data. |
| */ |
| }; |
| |
| static void |
| free_watches (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| _dbus_verbose ("start\n"); |
| |
| if (socket_transport->read_watch) |
| { |
| if (transport->connection) |
| _dbus_connection_remove_watch_unlocked (transport->connection, |
| socket_transport->read_watch); |
| _dbus_watch_invalidate (socket_transport->read_watch); |
| _dbus_watch_unref (socket_transport->read_watch); |
| socket_transport->read_watch = NULL; |
| } |
| |
| if (socket_transport->write_watch) |
| { |
| if (transport->connection) |
| _dbus_connection_remove_watch_unlocked (transport->connection, |
| socket_transport->write_watch); |
| _dbus_watch_invalidate (socket_transport->write_watch); |
| _dbus_watch_unref (socket_transport->write_watch); |
| socket_transport->write_watch = NULL; |
| } |
| |
| _dbus_verbose ("end\n"); |
| } |
| |
| static void |
| socket_finalize (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| _dbus_verbose ("\n"); |
| |
| free_watches (transport); |
| |
| _dbus_string_free (&socket_transport->encoded_outgoing); |
| _dbus_string_free (&socket_transport->encoded_incoming); |
| |
| _dbus_transport_finalize_base (transport); |
| |
| _dbus_assert (socket_transport->read_watch == NULL); |
| _dbus_assert (socket_transport->write_watch == NULL); |
| |
| dbus_free (transport); |
| } |
| |
| static void |
| check_write_watch (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| dbus_bool_t needed; |
| |
| if (transport->connection == NULL) |
| return; |
| |
| if (transport->disconnected) |
| { |
| _dbus_assert (socket_transport->write_watch == NULL); |
| return; |
| } |
| |
| _dbus_transport_ref (transport); |
| |
| if (_dbus_transport_get_is_authenticated (transport)) |
| needed = _dbus_connection_has_messages_to_send_unlocked (transport->connection); |
| else |
| { |
| if (transport->send_credentials_pending) |
| needed = TRUE; |
| else |
| { |
| DBusAuthState auth_state; |
| |
| auth_state = _dbus_auth_do_work (transport->auth); |
| |
| /* If we need memory we install the write watch just in case, |
| * if there's no need for it, it will get de-installed |
| * next time we try reading. |
| */ |
| if (auth_state == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND || |
| auth_state == DBUS_AUTH_STATE_WAITING_FOR_MEMORY) |
| needed = TRUE; |
| else |
| needed = FALSE; |
| } |
| } |
| |
| _dbus_verbose ("check_write_watch(): needed = %d on connection %p watch %p fd = %d outgoing messages exist %d\n", |
| needed, transport->connection, socket_transport->write_watch, |
| socket_transport->fd, |
| _dbus_connection_has_messages_to_send_unlocked (transport->connection)); |
| |
| _dbus_connection_toggle_watch_unlocked (transport->connection, |
| socket_transport->write_watch, |
| needed); |
| |
| _dbus_transport_unref (transport); |
| } |
| |
| static void |
| check_read_watch (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| dbus_bool_t need_read_watch; |
| |
| _dbus_verbose ("fd = %d\n",socket_transport->fd); |
| |
| if (transport->connection == NULL) |
| return; |
| |
| if (transport->disconnected) |
| { |
| _dbus_assert (socket_transport->read_watch == NULL); |
| return; |
| } |
| |
| _dbus_transport_ref (transport); |
| |
| if (_dbus_transport_get_is_authenticated (transport)) |
| need_read_watch = |
| (_dbus_counter_get_size_value (transport->live_messages) < transport->max_live_messages_size) && |
| (_dbus_counter_get_unix_fd_value (transport->live_messages) < transport->max_live_messages_unix_fds); |
| else |
| { |
| if (transport->receive_credentials_pending) |
| need_read_watch = TRUE; |
| else |
| { |
| /* The reason to disable need_read_watch when not WAITING_FOR_INPUT |
| * is to avoid spinning on the file descriptor when we're waiting |
| * to write or for some other part of the auth process |
| */ |
| DBusAuthState auth_state; |
| |
| auth_state = _dbus_auth_do_work (transport->auth); |
| |
| /* If we need memory we install the read watch just in case, |
| * if there's no need for it, it will get de-installed |
| * next time we try reading. If we're authenticated we |
| * install it since we normally have it installed while |
| * authenticated. |
| */ |
| if (auth_state == DBUS_AUTH_STATE_WAITING_FOR_INPUT || |
| auth_state == DBUS_AUTH_STATE_WAITING_FOR_MEMORY || |
| auth_state == DBUS_AUTH_STATE_AUTHENTICATED) |
| need_read_watch = TRUE; |
| else |
| need_read_watch = FALSE; |
| } |
| } |
| |
| _dbus_verbose (" setting read watch enabled = %d\n", need_read_watch); |
| _dbus_connection_toggle_watch_unlocked (transport->connection, |
| socket_transport->read_watch, |
| need_read_watch); |
| |
| _dbus_transport_unref (transport); |
| } |
| |
| static void |
| do_io_error (DBusTransport *transport) |
| { |
| _dbus_transport_ref (transport); |
| _dbus_transport_disconnect (transport); |
| _dbus_transport_unref (transport); |
| } |
| |
| /* return value is whether we successfully read any new data. */ |
| static dbus_bool_t |
| read_data_into_auth (DBusTransport *transport, |
| dbus_bool_t *oom) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| DBusString *buffer; |
| int bytes_read; |
| |
| *oom = FALSE; |
| |
| _dbus_auth_get_buffer (transport->auth, &buffer); |
| |
| bytes_read = _dbus_read_socket (socket_transport->fd, |
| buffer, socket_transport->max_bytes_read_per_iteration); |
| |
| _dbus_auth_return_buffer (transport->auth, buffer, |
| bytes_read > 0 ? bytes_read : 0); |
| |
| if (bytes_read > 0) |
| { |
| _dbus_verbose (" read %d bytes in auth phase\n", bytes_read); |
| |
| return TRUE; |
| } |
| else if (bytes_read < 0) |
| { |
| /* EINTR already handled for us */ |
| |
| if (_dbus_get_is_errno_enomem ()) |
| { |
| *oom = TRUE; |
| } |
| else if (_dbus_get_is_errno_eagain_or_ewouldblock ()) |
| ; /* do nothing, just return FALSE below */ |
| else |
| { |
| _dbus_verbose ("Error reading from remote app: %s\n", |
| _dbus_strerror_from_errno ()); |
| do_io_error (transport); |
| } |
| |
| return FALSE; |
| } |
| else |
| { |
| _dbus_assert (bytes_read == 0); |
| |
| _dbus_verbose ("Disconnected from remote app\n"); |
| do_io_error (transport); |
| |
| return FALSE; |
| } |
| } |
| |
| /* Return value is whether we successfully wrote any bytes */ |
| static dbus_bool_t |
| write_data_from_auth (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| int bytes_written; |
| const DBusString *buffer; |
| |
| if (!_dbus_auth_get_bytes_to_send (transport->auth, |
| &buffer)) |
| return FALSE; |
| |
| bytes_written = _dbus_write_socket (socket_transport->fd, |
| buffer, |
| 0, _dbus_string_get_length (buffer)); |
| |
| if (bytes_written > 0) |
| { |
| _dbus_auth_bytes_sent (transport->auth, bytes_written); |
| return TRUE; |
| } |
| else if (bytes_written < 0) |
| { |
| /* EINTR already handled for us */ |
| |
| if (_dbus_get_is_errno_eagain_or_ewouldblock ()) |
| ; |
| else |
| { |
| _dbus_verbose ("Error writing to remote app: %s\n", |
| _dbus_strerror_from_errno ()); |
| do_io_error (transport); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| /* FALSE on OOM */ |
| static dbus_bool_t |
| exchange_credentials (DBusTransport *transport, |
| dbus_bool_t do_reading, |
| dbus_bool_t do_writing) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| DBusError error = DBUS_ERROR_INIT; |
| |
| _dbus_verbose ("exchange_credentials: do_reading = %d, do_writing = %d\n", |
| do_reading, do_writing); |
| |
| if (do_writing && transport->send_credentials_pending) |
| { |
| if (_dbus_send_credentials_socket (socket_transport->fd, |
| &error)) |
| { |
| transport->send_credentials_pending = FALSE; |
| } |
| else |
| { |
| _dbus_verbose ("Failed to write credentials: %s\n", error.message); |
| dbus_error_free (&error); |
| do_io_error (transport); |
| } |
| } |
| |
| if (do_reading && transport->receive_credentials_pending) |
| { |
| /* FIXME this can fail due to IO error _or_ OOM, broken |
| * (somewhat tricky to fix since the OOM error can be set after |
| * we already read the credentials byte, so basically we need to |
| * separate reading the byte and storing it in the |
| * transport->credentials). Does not really matter for now |
| * because storing in credentials never actually fails on unix. |
| */ |
| if (_dbus_read_credentials_socket (socket_transport->fd, |
| transport->credentials, |
| &error)) |
| { |
| transport->receive_credentials_pending = FALSE; |
| } |
| else |
| { |
| _dbus_verbose ("Failed to read credentials %s\n", error.message); |
| dbus_error_free (&error); |
| do_io_error (transport); |
| } |
| } |
| |
| if (!(transport->send_credentials_pending || |
| transport->receive_credentials_pending)) |
| { |
| if (!_dbus_auth_set_credentials (transport->auth, |
| transport->credentials)) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| do_authentication (DBusTransport *transport, |
| dbus_bool_t do_reading, |
| dbus_bool_t do_writing, |
| dbus_bool_t *auth_completed) |
| { |
| dbus_bool_t oom; |
| dbus_bool_t orig_auth_state; |
| |
| oom = FALSE; |
| |
| orig_auth_state = _dbus_transport_get_is_authenticated (transport); |
| |
| /* This is essential to avoid the check_write_watch() at the end, |
| * we don't want to add a write watch in do_iteration before |
| * we try writing and get EAGAIN |
| */ |
| if (orig_auth_state) |
| { |
| if (auth_completed) |
| *auth_completed = FALSE; |
| return TRUE; |
| } |
| |
| _dbus_transport_ref (transport); |
| |
| while (!_dbus_transport_get_is_authenticated (transport) && |
| _dbus_transport_get_is_connected (transport)) |
| { |
| if (!exchange_credentials (transport, do_reading, do_writing)) |
| { |
| /* OOM */ |
| oom = TRUE; |
| goto out; |
| } |
| |
| if (transport->send_credentials_pending || |
| transport->receive_credentials_pending) |
| { |
| _dbus_verbose ("send_credentials_pending = %d receive_credentials_pending = %d\n", |
| transport->send_credentials_pending, |
| transport->receive_credentials_pending); |
| goto out; |
| } |
| |
| #define TRANSPORT_SIDE(t) ((t)->is_server ? "server" : "client") |
| switch (_dbus_auth_do_work (transport->auth)) |
| { |
| case DBUS_AUTH_STATE_WAITING_FOR_INPUT: |
| _dbus_verbose (" %s auth state: waiting for input\n", |
| TRANSPORT_SIDE (transport)); |
| if (!do_reading || !read_data_into_auth (transport, &oom)) |
| goto out; |
| break; |
| |
| case DBUS_AUTH_STATE_WAITING_FOR_MEMORY: |
| _dbus_verbose (" %s auth state: waiting for memory\n", |
| TRANSPORT_SIDE (transport)); |
| oom = TRUE; |
| goto out; |
| break; |
| |
| case DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND: |
| _dbus_verbose (" %s auth state: bytes to send\n", |
| TRANSPORT_SIDE (transport)); |
| if (!do_writing || !write_data_from_auth (transport)) |
| goto out; |
| break; |
| |
| case DBUS_AUTH_STATE_NEED_DISCONNECT: |
| _dbus_verbose (" %s auth state: need to disconnect\n", |
| TRANSPORT_SIDE (transport)); |
| do_io_error (transport); |
| break; |
| |
| case DBUS_AUTH_STATE_AUTHENTICATED: |
| _dbus_verbose (" %s auth state: authenticated\n", |
| TRANSPORT_SIDE (transport)); |
| break; |
| } |
| } |
| |
| out: |
| if (auth_completed) |
| *auth_completed = (orig_auth_state != _dbus_transport_get_is_authenticated (transport)); |
| |
| check_read_watch (transport); |
| check_write_watch (transport); |
| _dbus_transport_unref (transport); |
| |
| if (oom) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| |
| /* returns false on oom */ |
| static dbus_bool_t |
| do_writing (DBusTransport *transport) |
| { |
| int total; |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| dbus_bool_t oom; |
| |
| /* No messages without authentication! */ |
| if (!_dbus_transport_get_is_authenticated (transport)) |
| { |
| _dbus_verbose ("Not authenticated, not writing anything\n"); |
| return TRUE; |
| } |
| |
| if (transport->disconnected) |
| { |
| _dbus_verbose ("Not connected, not writing anything\n"); |
| return TRUE; |
| } |
| |
| #if 1 |
| _dbus_verbose ("do_writing(), have_messages = %d, fd = %d\n", |
| _dbus_connection_has_messages_to_send_unlocked (transport->connection), |
| socket_transport->fd); |
| #endif |
| |
| oom = FALSE; |
| total = 0; |
| |
| while (!transport->disconnected && |
| _dbus_connection_has_messages_to_send_unlocked (transport->connection)) |
| { |
| int bytes_written; |
| DBusMessage *message; |
| const DBusString *header; |
| const DBusString *body; |
| int header_len, body_len; |
| int total_bytes_to_write; |
| |
| if (total > socket_transport->max_bytes_written_per_iteration) |
| { |
| _dbus_verbose ("%d bytes exceeds %d bytes written per iteration, returning\n", |
| total, socket_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); |
| |
| #if 0 |
| _dbus_verbose ("writing message %p\n", message); |
| #endif |
| |
| _dbus_message_get_network_data (message, |
| &header, &body); |
| |
| header_len = _dbus_string_get_length (header); |
| body_len = _dbus_string_get_length (body); |
| |
| if (_dbus_auth_needs_encoding (transport->auth)) |
| { |
| /* Does fd passing even make sense with encoded data? */ |
| _dbus_assert(!DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)); |
| |
| if (_dbus_string_get_length (&socket_transport->encoded_outgoing) == 0) |
| { |
| if (!_dbus_auth_encode_data (transport->auth, |
| header, &socket_transport->encoded_outgoing)) |
| { |
| oom = TRUE; |
| goto out; |
| } |
| |
| if (!_dbus_auth_encode_data (transport->auth, |
| body, &socket_transport->encoded_outgoing)) |
| { |
| _dbus_string_set_length (&socket_transport->encoded_outgoing, 0); |
| oom = TRUE; |
| goto out; |
| } |
| } |
| |
| total_bytes_to_write = _dbus_string_get_length (&socket_transport->encoded_outgoing); |
| |
| #if 0 |
| _dbus_verbose ("encoded message is %d bytes\n", |
| total_bytes_to_write); |
| #endif |
| |
| bytes_written = |
| _dbus_write_socket (socket_transport->fd, |
| &socket_transport->encoded_outgoing, |
| socket_transport->message_bytes_written, |
| total_bytes_to_write - socket_transport->message_bytes_written); |
| } |
| else |
| { |
| total_bytes_to_write = header_len + body_len; |
| |
| #if 0 |
| _dbus_verbose ("message is %d bytes\n", |
| total_bytes_to_write); |
| #endif |
| |
| #ifdef HAVE_UNIX_FD_PASSING |
| if (socket_transport->message_bytes_written <= 0 && DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)) |
| { |
| /* Send the fds along with the first byte of the message */ |
| const int *unix_fds; |
| unsigned n; |
| |
| _dbus_message_get_unix_fds(message, &unix_fds, &n); |
| |
| bytes_written = |
| _dbus_write_socket_with_unix_fds_two (socket_transport->fd, |
| header, |
| socket_transport->message_bytes_written, |
| header_len - socket_transport->message_bytes_written, |
| body, |
| 0, body_len, |
| unix_fds, |
| n); |
| |
| if (bytes_written > 0 && n > 0) |
| _dbus_verbose("Wrote %i unix fds\n", n); |
| } |
| else |
| #endif |
| { |
| if (socket_transport->message_bytes_written < header_len) |
| { |
| bytes_written = |
| _dbus_write_socket_two (socket_transport->fd, |
| header, |
| socket_transport->message_bytes_written, |
| header_len - socket_transport->message_bytes_written, |
| body, |
| 0, body_len); |
| } |
| else |
| { |
| bytes_written = |
| _dbus_write_socket (socket_transport->fd, |
| body, |
| (socket_transport->message_bytes_written - header_len), |
| body_len - |
| (socket_transport->message_bytes_written - header_len)); |
| } |
| } |
| } |
| |
| if (bytes_written < 0) |
| { |
| /* EINTR already handled for us */ |
| |
| /* For some discussion of why we also ignore EPIPE here, see |
| * http://lists.freedesktop.org/archives/dbus/2008-March/009526.html |
| */ |
| |
| if (_dbus_get_is_errno_eagain_or_ewouldblock () || _dbus_get_is_errno_epipe ()) |
| goto out; |
| else |
| { |
| _dbus_verbose ("Error writing to remote app: %s\n", |
| _dbus_strerror_from_errno ()); |
| do_io_error (transport); |
| goto out; |
| } |
| } |
| else |
| { |
| _dbus_verbose (" wrote %d bytes of %d\n", bytes_written, |
| total_bytes_to_write); |
| |
| total += bytes_written; |
| socket_transport->message_bytes_written += bytes_written; |
| |
| _dbus_assert (socket_transport->message_bytes_written <= |
| total_bytes_to_write); |
| |
| if (socket_transport->message_bytes_written == total_bytes_to_write) |
| { |
| socket_transport->message_bytes_written = 0; |
| _dbus_string_set_length (&socket_transport->encoded_outgoing, 0); |
| _dbus_string_compact (&socket_transport->encoded_outgoing, 2048); |
| |
| _dbus_connection_message_sent (transport->connection, |
| message); |
| } |
| } |
| } |
| |
| out: |
| if (oom) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| |
| /* returns false on out-of-memory */ |
| static dbus_bool_t |
| do_reading (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| DBusString *buffer; |
| int bytes_read; |
| int total; |
| dbus_bool_t oom; |
| |
| _dbus_verbose ("fd = %d\n",socket_transport->fd); |
| |
| /* No messages without authentication! */ |
| if (!_dbus_transport_get_is_authenticated (transport)) |
| return TRUE; |
| |
| oom = FALSE; |
| |
| total = 0; |
| |
| again: |
| |
| /* See if we've exceeded max messages and need to disable reading */ |
| check_read_watch (transport); |
| |
| if (total > socket_transport->max_bytes_read_per_iteration) |
| { |
| _dbus_verbose ("%d bytes exceeds %d bytes read per iteration, returning\n", |
| total, socket_transport->max_bytes_read_per_iteration); |
| goto out; |
| } |
| |
| _dbus_assert (socket_transport->read_watch != NULL || |
| transport->disconnected); |
| |
| if (transport->disconnected) |
| goto out; |
| |
| if (!dbus_watch_get_enabled (socket_transport->read_watch)) |
| return TRUE; |
| |
| if (_dbus_auth_needs_decoding (transport->auth)) |
| { |
| /* Does fd passing even make sense with encoded data? */ |
| _dbus_assert(!DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)); |
| |
| if (_dbus_string_get_length (&socket_transport->encoded_incoming) > 0) |
| bytes_read = _dbus_string_get_length (&socket_transport->encoded_incoming); |
| else |
| bytes_read = _dbus_read_socket (socket_transport->fd, |
| &socket_transport->encoded_incoming, |
| socket_transport->max_bytes_read_per_iteration); |
| |
| _dbus_assert (_dbus_string_get_length (&socket_transport->encoded_incoming) == |
| bytes_read); |
| |
| if (bytes_read > 0) |
| { |
| int orig_len; |
| |
| _dbus_message_loader_get_buffer (transport->loader, |
| &buffer); |
| |
| orig_len = _dbus_string_get_length (buffer); |
| |
| if (!_dbus_auth_decode_data (transport->auth, |
| &socket_transport->encoded_incoming, |
| buffer)) |
| { |
| _dbus_verbose ("Out of memory decoding incoming data\n"); |
| _dbus_message_loader_return_buffer (transport->loader, |
| buffer, |
| _dbus_string_get_length (buffer) - orig_len); |
| |
| oom = TRUE; |
| goto out; |
| } |
| |
| _dbus_message_loader_return_buffer (transport->loader, |
| buffer, |
| _dbus_string_get_length (buffer) - orig_len); |
| |
| _dbus_string_set_length (&socket_transport->encoded_incoming, 0); |
| _dbus_string_compact (&socket_transport->encoded_incoming, 2048); |
| } |
| } |
| else |
| { |
| _dbus_message_loader_get_buffer (transport->loader, |
| &buffer); |
| |
| #ifdef HAVE_UNIX_FD_PASSING |
| if (DBUS_TRANSPORT_CAN_SEND_UNIX_FD(transport)) |
| { |
| int *fds, n_fds; |
| |
| if (!_dbus_message_loader_get_unix_fds(transport->loader, &fds, &n_fds)) |
| { |
| _dbus_verbose ("Out of memory reading file descriptors\n"); |
| _dbus_message_loader_return_buffer (transport->loader, buffer, 0); |
| oom = TRUE; |
| goto out; |
| } |
| |
| bytes_read = _dbus_read_socket_with_unix_fds(socket_transport->fd, |
| buffer, |
| socket_transport->max_bytes_read_per_iteration, |
| fds, &n_fds); |
| |
| if (bytes_read >= 0 && n_fds > 0) |
| _dbus_verbose("Read %i unix fds\n", n_fds); |
| |
| _dbus_message_loader_return_unix_fds(transport->loader, fds, bytes_read < 0 ? 0 : n_fds); |
| } |
| else |
| #endif |
| { |
| bytes_read = _dbus_read_socket (socket_transport->fd, |
| buffer, socket_transport->max_bytes_read_per_iteration); |
| } |
| |
| _dbus_message_loader_return_buffer (transport->loader, |
| buffer, |
| bytes_read < 0 ? 0 : bytes_read); |
| } |
| |
| if (bytes_read < 0) |
| { |
| /* EINTR already handled for us */ |
| |
| if (_dbus_get_is_errno_enomem ()) |
| { |
| _dbus_verbose ("Out of memory in read()/do_reading()\n"); |
| oom = TRUE; |
| goto out; |
| } |
| else if (_dbus_get_is_errno_eagain_or_ewouldblock ()) |
| goto out; |
| else |
| { |
| _dbus_verbose ("Error reading from remote app: %s\n", |
| _dbus_strerror_from_errno ()); |
| do_io_error (transport); |
| goto out; |
| } |
| } |
| else if (bytes_read == 0) |
| { |
| _dbus_verbose ("Disconnected from remote app\n"); |
| do_io_error (transport); |
| goto out; |
| } |
| else |
| { |
| _dbus_verbose (" read %d bytes\n", bytes_read); |
| |
| total += bytes_read; |
| |
| if (!_dbus_transport_queue_messages (transport)) |
| { |
| oom = TRUE; |
| _dbus_verbose (" out of memory when queueing messages we just read in the transport\n"); |
| goto out; |
| } |
| |
| /* 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: |
| if (oom) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| unix_error_with_read_to_come (DBusTransport *itransport, |
| DBusWatch *watch, |
| unsigned int flags) |
| { |
| DBusTransportSocket *transport = (DBusTransportSocket *) itransport; |
| |
| if (!(flags & DBUS_WATCH_HANGUP || flags & DBUS_WATCH_ERROR)) |
| return FALSE; |
| |
| /* If we have a read watch enabled ... |
| we -might have data incoming ... => handle the HANGUP there */ |
| if (watch != transport->read_watch && |
| _dbus_watch_get_enabled (transport->read_watch)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| socket_handle_watch (DBusTransport *transport, |
| DBusWatch *watch, |
| unsigned int flags) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| _dbus_assert (watch == socket_transport->read_watch || |
| watch == socket_transport->write_watch); |
| _dbus_assert (watch != NULL); |
| |
| /* If we hit an error here on a write watch, don't disconnect the transport yet because data can |
| * still be in the buffer and do_reading may need several iteration to read |
| * it all (because of its max_bytes_read_per_iteration limit). |
| */ |
| if (!(flags & DBUS_WATCH_READABLE) && unix_error_with_read_to_come (transport, watch, flags)) |
| { |
| _dbus_verbose ("Hang up or error on watch\n"); |
| _dbus_transport_disconnect (transport); |
| return TRUE; |
| } |
| |
| if (watch == socket_transport->read_watch && |
| (flags & DBUS_WATCH_READABLE)) |
| { |
| dbus_bool_t auth_finished; |
| #if 1 |
| _dbus_verbose ("handling read watch %p flags = %x\n", |
| watch, flags); |
| #endif |
| if (!do_authentication (transport, TRUE, FALSE, &auth_finished)) |
| return FALSE; |
| |
| /* We don't want to do a read immediately following |
| * a successful authentication. This is so we |
| * have a chance to propagate the authentication |
| * state further up. Specifically, we need to |
| * process any pending data from the auth object. |
| */ |
| if (!auth_finished) |
| { |
| if (!do_reading (transport)) |
| { |
| _dbus_verbose ("no memory to read\n"); |
| return FALSE; |
| } |
| } |
| else |
| { |
| _dbus_verbose ("Not reading anything since we just completed the authentication\n"); |
| } |
| } |
| else if (watch == socket_transport->write_watch && |
| (flags & DBUS_WATCH_WRITABLE)) |
| { |
| #if 1 |
| _dbus_verbose ("handling write watch, have_outgoing_messages = %d\n", |
| _dbus_connection_has_messages_to_send_unlocked (transport->connection)); |
| #endif |
| if (!do_authentication (transport, FALSE, TRUE, NULL)) |
| return FALSE; |
| |
| if (!do_writing (transport)) |
| { |
| _dbus_verbose ("no memory to write\n"); |
| return FALSE; |
| } |
| |
| /* See if we still need the write watch */ |
| check_write_watch (transport); |
| } |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| else |
| { |
| if (watch == socket_transport->read_watch) |
| _dbus_verbose ("asked to handle read watch with non-read condition 0x%x\n", |
| flags); |
| else if (watch == socket_transport->write_watch) |
| _dbus_verbose ("asked to handle write watch with non-write condition 0x%x\n", |
| flags); |
| else |
| _dbus_verbose ("asked to handle watch %p on fd %d that we don't recognize\n", |
| watch, dbus_watch_get_socket (watch)); |
| } |
| #endif /* DBUS_ENABLE_VERBOSE_MODE */ |
| |
| return TRUE; |
| } |
| |
| static void |
| socket_disconnect (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| _dbus_verbose ("\n"); |
| |
| free_watches (transport); |
| |
| _dbus_close_socket (socket_transport->fd, NULL); |
| socket_transport->fd = -1; |
| } |
| |
| static dbus_bool_t |
| socket_connection_set (DBusTransport *transport) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| _dbus_watch_set_handler (socket_transport->write_watch, |
| _dbus_connection_handle_watch, |
| transport->connection, NULL); |
| |
| _dbus_watch_set_handler (socket_transport->read_watch, |
| _dbus_connection_handle_watch, |
| transport->connection, NULL); |
| |
| if (!_dbus_connection_add_watch_unlocked (transport->connection, |
| socket_transport->write_watch)) |
| return FALSE; |
| |
| if (!_dbus_connection_add_watch_unlocked (transport->connection, |
| socket_transport->read_watch)) |
| { |
| _dbus_connection_remove_watch_unlocked (transport->connection, |
| socket_transport->write_watch); |
| return FALSE; |
| } |
| |
| check_read_watch (transport); |
| check_write_watch (transport); |
| |
| return TRUE; |
| } |
| |
| /** |
| * @todo We need to have a way to wake up the select sleep if |
| * a new iteration request comes in with a flag (read/write) that |
| * we're not currently serving. Otherwise a call that just reads |
| * could block a write call forever (if there are no incoming |
| * messages). |
| */ |
| static void |
| socket_do_iteration (DBusTransport *transport, |
| unsigned int flags, |
| int timeout_milliseconds) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| DBusPollFD poll_fd; |
| int poll_res; |
| int poll_timeout; |
| |
| _dbus_verbose (" iteration flags = %s%s timeout = %d read_watch = %p write_watch = %p fd = %d\n", |
| flags & DBUS_ITERATION_DO_READING ? "read" : "", |
| flags & DBUS_ITERATION_DO_WRITING ? "write" : "", |
| timeout_milliseconds, |
| socket_transport->read_watch, |
| socket_transport->write_watch, |
| socket_transport->fd); |
| |
| /* the passed in DO_READING/DO_WRITING flags indicate whether to |
| * read/write messages, but regardless of those we may need to block |
| * for reading/writing to do auth. But if we do reading for auth, |
| * we don't want to read any messages yet if not given DO_READING. |
| */ |
| |
| poll_fd.fd = socket_transport->fd; |
| poll_fd.events = 0; |
| |
| if (_dbus_transport_get_is_authenticated (transport)) |
| { |
| /* This is kind of a hack; if we have stuff to write, then try |
| * to avoid the poll. This is probably about a 5% speedup on an |
| * echo client/server. |
| * |
| * If both reading and writing were requested, we want to avoid this |
| * since it could have funky effects: |
| * - both ends spinning waiting for the other one to read |
| * data so they can finish writing |
| * - prioritizing all writing ahead of reading |
| */ |
| if ((flags & DBUS_ITERATION_DO_WRITING) && |
| !(flags & (DBUS_ITERATION_DO_READING | DBUS_ITERATION_BLOCK)) && |
| !transport->disconnected && |
| _dbus_connection_has_messages_to_send_unlocked (transport->connection)) |
| { |
| do_writing (transport); |
| |
| if (transport->disconnected || |
| !_dbus_connection_has_messages_to_send_unlocked (transport->connection)) |
| goto out; |
| } |
| |
| /* If we get here, we decided to do the poll() after all */ |
| _dbus_assert (socket_transport->read_watch); |
| if (flags & DBUS_ITERATION_DO_READING) |
| poll_fd.events |= _DBUS_POLLIN; |
| |
| _dbus_assert (socket_transport->write_watch); |
| if (flags & DBUS_ITERATION_DO_WRITING) |
| poll_fd.events |= _DBUS_POLLOUT; |
| } |
| else |
| { |
| DBusAuthState auth_state; |
| |
| auth_state = _dbus_auth_do_work (transport->auth); |
| |
| if (transport->receive_credentials_pending || |
| auth_state == DBUS_AUTH_STATE_WAITING_FOR_INPUT) |
| poll_fd.events |= _DBUS_POLLIN; |
| |
| if (transport->send_credentials_pending || |
| auth_state == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND) |
| poll_fd.events |= _DBUS_POLLOUT; |
| } |
| |
| if (poll_fd.events) |
| { |
| if (flags & DBUS_ITERATION_BLOCK) |
| poll_timeout = timeout_milliseconds; |
| else |
| poll_timeout = 0; |
| |
| /* For blocking selects we drop the connection lock here |
| * to avoid blocking out connection access during a potentially |
| * indefinite blocking call. The io path is still protected |
| * by the io_path_cond condvar, so we won't reenter this. |
| */ |
| if (flags & DBUS_ITERATION_BLOCK) |
| { |
| _dbus_verbose ("unlock pre poll\n"); |
| _dbus_connection_unlock (transport->connection); |
| } |
| |
| again: |
| poll_res = _dbus_poll (&poll_fd, 1, poll_timeout); |
| |
| if (poll_res < 0 && _dbus_get_is_errno_eintr ()) |
| goto again; |
| |
| if (flags & DBUS_ITERATION_BLOCK) |
| { |
| _dbus_verbose ("lock post poll\n"); |
| _dbus_connection_lock (transport->connection); |
| } |
| |
| if (poll_res >= 0) |
| { |
| if (poll_res == 0) |
| poll_fd.revents = 0; /* some concern that posix does not guarantee this; |
| * valgrind flags it as an error. though it probably |
| * is guaranteed on linux at least. |
| */ |
| |
| if (poll_fd.revents & _DBUS_POLLERR) |
| do_io_error (transport); |
| else |
| { |
| dbus_bool_t need_read = (poll_fd.revents & _DBUS_POLLIN) > 0; |
| dbus_bool_t need_write = (poll_fd.revents & _DBUS_POLLOUT) > 0; |
| dbus_bool_t authentication_completed; |
| |
| _dbus_verbose ("in iteration, need_read=%d need_write=%d\n", |
| need_read, need_write); |
| do_authentication (transport, need_read, need_write, |
| &authentication_completed); |
| |
| /* See comment in socket_handle_watch. */ |
| if (authentication_completed) |
| goto out; |
| |
| if (need_read && (flags & DBUS_ITERATION_DO_READING)) |
| do_reading (transport); |
| if (need_write && (flags & DBUS_ITERATION_DO_WRITING)) |
| do_writing (transport); |
| } |
| } |
| else |
| { |
| _dbus_verbose ("Error from _dbus_poll(): %s\n", |
| _dbus_strerror_from_errno ()); |
| } |
| } |
| |
| |
| out: |
| /* We need to install the write watch only if we did not |
| * successfully write everything. Note we need to be careful that we |
| * don't call check_write_watch *before* do_writing, since it's |
| * inefficient to add the write watch, and we can avoid it most of |
| * the time since we can write immediately. |
| * |
| * However, we MUST always call check_write_watch(); DBusConnection code |
| * relies on the fact that running an iteration will notice that |
| * messages are pending. |
| */ |
| check_write_watch (transport); |
| |
| _dbus_verbose (" ... leaving do_iteration()\n"); |
| } |
| |
| static void |
| socket_live_messages_changed (DBusTransport *transport) |
| { |
| /* See if we should look for incoming messages again */ |
| check_read_watch (transport); |
| } |
| |
| |
| static dbus_bool_t |
| socket_get_socket_fd (DBusTransport *transport, |
| int *fd_p) |
| { |
| DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; |
| |
| *fd_p = socket_transport->fd; |
| |
| return TRUE; |
| } |
| |
| static const DBusTransportVTable socket_vtable = { |
| socket_finalize, |
| socket_handle_watch, |
| socket_disconnect, |
| socket_connection_set, |
| socket_do_iteration, |
| socket_live_messages_changed, |
| socket_get_socket_fd |
| }; |
| |
| /** |
| * Creates a new transport for the given socket 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. |
| * @param server_guid non-#NULL if this transport is on the server side of a connection |
| * @param address the transport's address |
| * @returns the new transport, or #NULL if no memory. |
| */ |
| DBusTransport* |
| _dbus_transport_new_for_socket (int fd, |
| const DBusString *server_guid, |
| const DBusString *address) |
| { |
| DBusTransportSocket *socket_transport; |
| |
| socket_transport = dbus_new0 (DBusTransportSocket, 1); |
| if (socket_transport == NULL) |
| return NULL; |
| |
| if (!_dbus_string_init (&socket_transport->encoded_outgoing)) |
| goto failed_0; |
| |
| if (!_dbus_string_init (&socket_transport->encoded_incoming)) |
| goto failed_1; |
| |
| socket_transport->write_watch = _dbus_watch_new (fd, |
| DBUS_WATCH_WRITABLE, |
| FALSE, |
| NULL, NULL, NULL); |
| if (socket_transport->write_watch == NULL) |
| goto failed_2; |
| |
| socket_transport->read_watch = _dbus_watch_new (fd, |
| DBUS_WATCH_READABLE, |
| FALSE, |
| NULL, NULL, NULL); |
| if (socket_transport->read_watch == NULL) |
| goto failed_3; |
| |
| if (!_dbus_transport_init_base (&socket_transport->base, |
| &socket_vtable, |
| server_guid, address)) |
| goto failed_4; |
| |
| #ifdef HAVE_UNIX_FD_PASSING |
| _dbus_auth_set_unix_fd_possible(socket_transport->base.auth, _dbus_socket_can_pass_unix_fd(fd)); |
| #endif |
| |
| socket_transport->fd = fd; |
| socket_transport->message_bytes_written = 0; |
| |
| /* These values should probably be tunable or something. */ |
| socket_transport->max_bytes_read_per_iteration = 2048; |
| socket_transport->max_bytes_written_per_iteration = 2048; |
| |
| return (DBusTransport*) socket_transport; |
| |
| failed_4: |
| _dbus_watch_unref (socket_transport->read_watch); |
| failed_3: |
| _dbus_watch_unref (socket_transport->write_watch); |
| failed_2: |
| _dbus_string_free (&socket_transport->encoded_incoming); |
| failed_1: |
| _dbus_string_free (&socket_transport->encoded_outgoing); |
| failed_0: |
| dbus_free (socket_transport); |
| return NULL; |
| } |
| |
| /** |
| * Creates a new transport for the given hostname and port. |
| * If host is NULL, it will default to localhost |
| * |
| * @param host the host to connect to |
| * @param port the port to connect to |
| * @param family the address family to connect to |
| * @param path to nonce file |
| * @param error location to store reason for failure. |
| * @returns a new transport, or #NULL on failure. |
| */ |
| DBusTransport* |
| _dbus_transport_new_for_tcp_socket (const char *host, |
| const char *port, |
| const char *family, |
| const char *noncefile, |
| DBusError *error) |
| { |
| int fd; |
| DBusTransport *transport; |
| DBusString address; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| if (!_dbus_string_init (&address)) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| return NULL; |
| } |
| |
| if (host == NULL) |
| host = "localhost"; |
| |
| if (!_dbus_string_append (&address, noncefile ? "nonce-tcp:" : "tcp:")) |
| goto error; |
| |
| if (!_dbus_string_append (&address, "host=") || |
| !_dbus_string_append (&address, host)) |
| goto error; |
| |
| if (!_dbus_string_append (&address, ",port=") || |
| !_dbus_string_append (&address, port)) |
| goto error; |
| |
| if (family != NULL && |
| (!_dbus_string_append (&address, "family=") || |
| !_dbus_string_append (&address, family))) |
| goto error; |
| |
| if (noncefile != NULL && |
| (!_dbus_string_append (&address, "noncefile=") || |
| !_dbus_string_append (&address, noncefile))) |
| goto error; |
| |
| fd = _dbus_connect_tcp_socket_with_nonce (host, port, family, noncefile, error); |
| if (fd < 0) |
| { |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| _dbus_string_free (&address); |
| return NULL; |
| } |
| |
| _dbus_verbose ("Successfully connected to tcp socket %s:%s\n", |
| host, port); |
| |
| transport = _dbus_transport_new_for_socket (fd, NULL, &address); |
| _dbus_string_free (&address); |
| if (transport == NULL) |
| { |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| _dbus_close_socket (fd, NULL); |
| fd = -1; |
| } |
| |
| return transport; |
| |
| error: |
| _dbus_string_free (&address); |
| dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); |
| return NULL; |
| } |
| |
| /** |
| * Opens a TCP socket transport. |
| * |
| * @param entry the address entry to try opening as a tcp transport. |
| * @param transport_p return location for the opened transport |
| * @param error error to be set |
| * @returns result of the attempt |
| */ |
| DBusTransportOpenResult |
| _dbus_transport_open_socket(DBusAddressEntry *entry, |
| DBusTransport **transport_p, |
| DBusError *error) |
| { |
| const char *method; |
| dbus_bool_t isTcp; |
| dbus_bool_t isNonceTcp; |
| |
| method = dbus_address_entry_get_method (entry); |
| _dbus_assert (method != NULL); |
| |
| isTcp = strcmp (method, "tcp") == 0; |
| isNonceTcp = strcmp (method, "nonce-tcp") == 0; |
| |
| if (isTcp || isNonceTcp) |
| { |
| const char *host = dbus_address_entry_get_value (entry, "host"); |
| const char *port = dbus_address_entry_get_value (entry, "port"); |
| const char *family = dbus_address_entry_get_value (entry, "family"); |
| const char *noncefile = dbus_address_entry_get_value (entry, "noncefile"); |
| |
| if ((isNonceTcp == TRUE) != (noncefile != NULL)) { |
| _dbus_set_bad_address (error, method, "noncefile", NULL); |
| return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; |
| } |
| |
| if (port == NULL) |
| { |
| _dbus_set_bad_address (error, method, "port", NULL); |
| return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; |
| } |
| |
| *transport_p = _dbus_transport_new_for_tcp_socket (host, port, family, noncefile, error); |
| if (*transport_p == NULL) |
| { |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; |
| } |
| else |
| { |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| return DBUS_TRANSPORT_OPEN_OK; |
| } |
| } |
| else |
| { |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| return DBUS_TRANSPORT_OPEN_NOT_HANDLED; |
| } |
| } |
| |
| /** @} */ |
| |