diff options
-rw-r--r-- | gdbus/gdbusconnection.c | 115 | ||||
-rw-r--r-- | gdbus/gdbusenums.h | 2 | ||||
-rw-r--r-- | gdbus/gdbusmessage.c | 47 | ||||
-rw-r--r-- | gdbus/gdbusmessage.h | 4 | ||||
-rw-r--r-- | gdbus/gdbusprivate.c | 264 | ||||
-rw-r--r-- | gdbus/tests/peer.c | 104 |
6 files changed, 497 insertions, 39 deletions
diff --git a/gdbus/gdbusconnection.c b/gdbus/gdbusconnection.c index bc6f028..fb5e9d6 100644 --- a/gdbus/gdbusconnection.c +++ b/gdbus/gdbusconnection.c @@ -28,6 +28,7 @@ #ifdef G_OS_UNIX #include <gio/gunixconnection.h> +#include <gio/gunixfdmessage.h> #endif #include <unistd.h> @@ -819,12 +820,12 @@ static gboolean g_dbus_connection_write (GDBusConnection *connection, const gchar *blob, gssize blob_size, + GUnixFDList *fd_list, GError **error) { gboolean ret; - gsize total_bytes_written; - gsize remaining; - gssize bytes_written; + + ret = FALSE; if (connection->priv->closed) { @@ -835,32 +836,93 @@ g_dbus_connection_write (GDBusConnection *connection, goto out; } + /* TODO: do this in private thread */ + if (blob_size == -1) blob_size = strlen (blob); - ret = FALSE; - remaining = blob_size; - total_bytes_written = 0; - while (remaining > 0) - { - /* TODO: do this async in private thread */ - bytes_written = g_output_stream_write (g_io_stream_get_output_stream (connection->priv->stream), - (const gchar *) blob + total_bytes_written, - remaining, - NULL, - error); - if (bytes_written == -1) - { - g_prefix_error (error, _("Error writing blob of %" G_GSIZE_FORMAT " bytes to socket: "), remaining); - goto out; - } + g_assert (blob_size > 16); - /* TODO: hmm, what if zero is returned? the socket is non-blocking so g_socket_send() should block... */ - g_assert (bytes_written != 0); + /* First, the initial 16 bytes - special case UNIX sockets here + * since it may involve writing an ancillary message + */ +#ifdef G_OS_UNIX + { + GOutputVector vector; + GSocketControlMessage *message; + GSocket *socket; + gssize bytes_written; - total_bytes_written += bytes_written; - remaining -= bytes_written; - } + message = NULL; + if (fd_list != NULL) + { + if (!G_IS_UNIX_CONNECTION (connection->priv->stream)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Tried sending a file descriptor on unsupported stream of type %s", + g_type_name (G_TYPE_FROM_INSTANCE (connection->priv->stream))); + goto out; + } + else if (!(connection->priv->capabilities & G_DBUS_CONNECTION_CAPABILITY_FLAGS_UNIX_FD_PASSING)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Tried sending a file descriptor but remote peer does not support this capability"); + goto out; + } + message = g_unix_fd_message_new_with_fd_list (fd_list); + } + + socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection->priv->stream)); + vector.buffer = blob; + vector.size = 16; + + bytes_written = g_socket_send_message (socket, + NULL, /* address */ + &vector, + 1, + message != NULL ? &message : NULL, + message != NULL ? 1 : 0, + G_SOCKET_MSG_NONE, + NULL, /* cancellable */ + error); + if (bytes_written == -1) + { + g_prefix_error (error, _("Error writing file descriptors to socket: ")); + g_object_unref (message); + goto out; + } + if (bytes_written < 16) + { + /* TODO: I think this needs to be handled ... are we guaranteed that the ancillary + * messages are sent? + */ + g_assert_not_reached (); + } + if (message != NULL) + g_object_unref (message); + } +#else + if (!g_output_stream_write_all (g_io_stream_get_output_stream (connection->priv->stream), + (const gchar *) blob, + 16, + NULL, /* bytes_written */ + NULL, /* cancellable */ + error)) + goto out; +#endif + + /* Then write the rest of the message */ + if (!g_output_stream_write_all (g_io_stream_get_output_stream (connection->priv->stream), + (const gchar *) blob + 16, + blob_size - 16, + NULL, /* bytes_written */ + NULL, /* cancellable */ + error)) + goto out; ret = TRUE; @@ -932,6 +994,11 @@ g_dbus_connection_send_message_unlocked (GDBusConnection *connection, if (!g_dbus_connection_write (connection, (const gchar *) blob, blob_size, +#ifdef G_OS_UNIX + g_dbus_message_get_unix_fd_list (message), +#else + NULL, +#endif error)) goto out; diff --git a/gdbus/gdbusenums.h b/gdbus/gdbusenums.h index 4d15ed2..ea85995 100644 --- a/gdbus/gdbusenums.h +++ b/gdbus/gdbusenums.h @@ -311,6 +311,7 @@ typedef enum { * @G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: The name the message is intended for. * @G_DBUS_MESSAGE_HEADER_FIELD_SENDER: Unique name of the sender of the message (filled in by the bus). * @G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: The signature of the message body. + * @G_DBUS_MESSAGE_HEADER_FIELD_UNIX_FDS: The number of UNIX file descriptors that accompany the message. * * Header fields used in #GDBusMessage. */ @@ -324,6 +325,7 @@ typedef enum { G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, G_DBUS_MESSAGE_HEADER_FIELD_SENDER, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, + G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS } GDBusMessageHeaderField; /** diff --git a/gdbus/gdbusmessage.c b/gdbus/gdbusmessage.c index f2fd90c..2bf5f84 100644 --- a/gdbus/gdbusmessage.c +++ b/gdbus/gdbusmessage.c @@ -356,7 +356,16 @@ g_dbus_message_set_unix_fd_list (GDBusMessage *message, g_return_if_fail (G_IS_DBUS_MESSAGE (message)); if (message->priv->fd_list != NULL) g_object_unref (message->priv->fd_list); - message->priv->fd_list = fd_list != NULL ? g_object_ref (fd_list) : NULL; + if (fd_list != NULL) + { + message->priv->fd_list = g_object_ref (fd_list); + g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list)); + } + else + { + message->priv->fd_list = NULL; + g_dbus_message_set_num_unix_fds (message, 0); + } } #endif @@ -1246,6 +1255,8 @@ g_dbus_message_to_blob (GDBusMessage *message, GVariant *header_value; GVariant *signature; const gchar *signature_str; + gint num_fds_in_message; + gint num_fds_according_to_header; ret = NULL; @@ -1270,7 +1281,23 @@ g_dbus_message_to_blob (GDBusMessage *message, g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL); g_data_output_stream_put_uint32 (dos, message->priv->serial, NULL, NULL); - /* TODO: check we have all the right header fields etc etc */ + num_fds_in_message = 0; +#ifdef G_OS_UNIX + if (message->priv->fd_list != NULL) + num_fds_in_message = g_unix_fd_list_get_length (message->priv->fd_list); +#endif + num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message); + /* TODO: check we have all the right header fields and that they are the correct value etc etc */ + if (num_fds_in_message != num_fds_according_to_header) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Message has %d fds but the header field indicates %d fds"), + num_fds_in_message, + num_fds_according_to_header); + goto out; + } builder = g_variant_builder_new (G_VARIANT_TYPE ("a{yv}"));//G_VARIANT_TYPE_ARRAY); g_hash_table_iter_init (&hash_iter, message->priv->headers); @@ -1627,3 +1654,19 @@ g_dbus_message_get_arg0 (GDBusMessage *message) /* ---------------------------------------------------------------------------------------------------- */ +guint32 +g_dbus_message_get_num_unix_fds (GDBusMessage *message) +{ + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); + return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS); +} + +void +g_dbus_message_set_num_unix_fds (GDBusMessage *message, + guint32 value) +{ + g_return_if_fail (G_IS_DBUS_MESSAGE (message)); + set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS, value); +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/gdbus/gdbusmessage.h b/gdbus/gdbusmessage.h index 8ef22e4..499d158 100644 --- a/gdbus/gdbusmessage.h +++ b/gdbus/gdbusmessage.h @@ -134,6 +134,10 @@ const gchar *g_dbus_message_get_signature (GDBusMessage void g_dbus_message_set_signature (GDBusMessage *message, const gchar *value); +guint32 g_dbus_message_get_num_unix_fds (GDBusMessage *message); +void g_dbus_message_set_num_unix_fds (GDBusMessage *message, + guint32 value); + const gchar *g_dbus_message_get_arg0 (GDBusMessage *message); diff --git a/gdbus/gdbusprivate.c b/gdbus/gdbusprivate.c index a130b8f..0723a9f 100644 --- a/gdbus/gdbusprivate.c +++ b/gdbus/gdbusprivate.c @@ -27,6 +27,12 @@ #include <glib/gi18n.h> +#ifdef G_OS_UNIX +#include <gio/gunixconnection.h> +#include <gio/gunixfdmessage.h> +#include <unistd.h> +#endif + #include "gdbustypes.h" #include "gdbusprivate.h" #include "gdbusmessage.h" @@ -34,6 +40,144 @@ /* ---------------------------------------------------------------------------------------------------- */ +/* Unfortunately ancillary messages are discarded when reading from a + * socket using the GSocketInputStream abstraction. So we provide a + * very GInputStream-ish API that uses GSocket in this case (very + * similar to GSocketInputStream). + */ + +typedef struct +{ + GSocket *socket; + GCancellable *cancellable; + + void *buffer; + gsize count; + + GSocketControlMessage ***messages; + gint *num_messages; + + GSimpleAsyncResult *simple; + + gboolean from_mainloop; +} ReadWithControlData; + +static void +read_with_control_data_free (ReadWithControlData *data) +{ + g_object_unref (data->socket); + if (data->cancellable != NULL) + g_object_unref (data->cancellable); + g_free (data); +} + +static gboolean +_g_socket_read_with_control_messages_ready (GSocket *socket, + GIOCondition condition, + gpointer user_data) +{ + ReadWithControlData *data = user_data; + GError *error; + gssize result; + GInputVector vector; + + error = NULL; + vector.buffer = data->buffer; + vector.size = data->count; + result = g_socket_receive_message (data->socket, + NULL, /* address */ + &vector, + 1, + data->messages, + data->num_messages, + NULL, + data->cancellable, + &error); + + if (result >= 0) + { + g_simple_async_result_set_op_res_gssize (data->simple, result); + } + else + { + g_assert (error != NULL); + g_simple_async_result_set_from_error (data->simple, error); + g_error_free (error); + } + + if (data->from_mainloop) + g_simple_async_result_complete (data->simple); + else + g_simple_async_result_complete_in_idle (data->simple); + + return FALSE; +} + +static void +_g_socket_read_with_control_messages (GSocket *socket, + void *buffer, + gsize count, + GSocketControlMessage ***messages, + gint *num_messages, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ReadWithControlData *data; + + data = g_new0 (ReadWithControlData, 1); + data->socket = g_object_ref (socket); + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + data->buffer = buffer; + data->count = count; + data->messages = messages; + data->num_messages = num_messages; + + data->simple = g_simple_async_result_new (G_OBJECT (socket), + callback, + user_data, + _g_socket_read_with_control_messages); + + if (!g_socket_condition_check (socket, G_IO_IN)) + { + GSource *source; + data->from_mainloop = TRUE; + source = g_socket_create_source (data->socket, + G_IO_IN | G_IO_HUP | G_IO_ERR, + cancellable); + g_source_set_callback (source, + (GSourceFunc) _g_socket_read_with_control_messages_ready, + data, + (GDestroyNotify) read_with_control_data_free); + g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); + } + else + { + _g_socket_read_with_control_messages_ready (data->socket, G_IO_IN, data); + read_with_control_data_free (data); + } +} + +static gssize +_g_socket_read_with_control_messages_finish (GSocket *socket, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + g_return_val_if_fail (G_IS_SOCKET (socket), -1); + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == _g_socket_read_with_control_messages); + + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + else + return g_simple_async_result_get_op_res_gssize (simple); +} + +/* ---------------------------------------------------------------------------------------------------- */ + G_LOCK_DEFINE_STATIC (shared_thread_lock); typedef struct @@ -171,6 +315,13 @@ struct GDBusWorker gsize read_buffer_allocated_size; gsize read_buffer_cur_size; gsize read_buffer_bytes_wanted; + GUnixFDList *fd_list; + + GSocketControlMessage **read_ancillary_messages; + gint read_num_ancillary_messages; + + /* if not NULL, stream is GSocketConnection */ + GSocket *socket; }; static GDBusWorker * @@ -190,6 +341,8 @@ _g_dbus_worker_unref (GDBusWorker *worker) g_object_unref (worker->stream); g_mutex_free (worker->lock); g_object_unref (worker->cancellable); + if (worker->fd_list != NULL) + g_object_unref (worker->fd_list); g_free (worker); } } @@ -211,7 +364,6 @@ _g_dbus_worker_emit_message (GDBusWorker *worker, worker->message_received_callback (worker, message, worker->user_data); } - static void _g_dbus_worker_do_read_unlocked (GDBusWorker *worker); /* called in private thread shared by all GDBusConnection instances (without lock held) */ @@ -231,9 +383,75 @@ _g_dbus_worker_do_read_cb (GInputStream *input_stream, goto out; error = NULL; - bytes_read = g_input_stream_read_finish (g_io_stream_get_input_stream (worker->stream), - res, - &error); + if (worker->socket == NULL) + bytes_read = g_input_stream_read_finish (g_io_stream_get_input_stream (worker->stream), + res, + &error); + else + bytes_read = _g_socket_read_with_control_messages_finish (worker->socket, + res, + &error); + + if (worker->read_num_ancillary_messages > 0) + { + gint n; + for (n = 0; n < worker->read_num_ancillary_messages; n++) + { + GSocketControlMessage *control_message = G_SOCKET_CONTROL_MESSAGE (worker->read_ancillary_messages[n]); + + if (FALSE) + { + } +#ifdef G_OS_UNIX + else if (G_IS_UNIX_FD_MESSAGE (control_message)) + { + GUnixFDMessage *fd_message; + gint *fds; + gint num_fds; + + fd_message = G_UNIX_FD_MESSAGE (control_message); + fds = g_unix_fd_message_steal_fds (fd_message, &num_fds); + if (worker->fd_list == NULL) + { + worker->fd_list = g_unix_fd_list_new_from_array (fds, num_fds); + } + else + { + gint n; + for (n = 0; n < num_fds; n++) + { + /* TODO: really want a append_steal() */ + g_unix_fd_list_append (worker->fd_list, fds[n], NULL); + close (fds[n]); + } + } + g_free (fds); + } +#endif + else + { + if (error == NULL) + { + g_set_error (&error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unexpected ancillary message of type %s received from peer", + g_type_name (G_TYPE_FROM_INSTANCE (control_message))); + _g_dbus_worker_emit_disconnected (worker, TRUE, error); + g_error_free (error); + g_object_unref (control_message); + n++; + while (n < worker->read_num_ancillary_messages) + g_object_unref (worker->read_ancillary_messages[n++]); + g_free (worker->read_ancillary_messages); + goto out; + } + } + g_object_unref (control_message); + } + g_free (worker->read_ancillary_messages); + } + if (bytes_read == -1) { _g_dbus_worker_emit_disconnected (worker, TRUE, error); @@ -304,6 +522,12 @@ _g_dbus_worker_do_read_cb (GInputStream *input_stream, goto out; } + if (worker->fd_list != NULL) + { + g_dbus_message_set_unix_fd_list (message, worker->fd_list); + worker->fd_list = NULL; + } + /* yay, got a message, go deliver it */ _g_dbus_worker_emit_message (worker, message); g_object_unref (message); @@ -346,13 +570,28 @@ _g_dbus_worker_do_read_unlocked (GDBusWorker *worker) worker->read_buffer = g_realloc (worker->read_buffer, worker->read_buffer_allocated_size); } - g_input_stream_read_async (g_io_stream_get_input_stream (worker->stream), - worker->read_buffer + worker->read_buffer_cur_size, - worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size, - G_PRIORITY_DEFAULT, - worker->cancellable, - (GAsyncReadyCallback) _g_dbus_worker_do_read_cb, - _g_dbus_worker_ref (worker)); + if (worker->socket == NULL) + g_input_stream_read_async (g_io_stream_get_input_stream (worker->stream), + worker->read_buffer + worker->read_buffer_cur_size, + worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size, + G_PRIORITY_DEFAULT, + worker->cancellable, + (GAsyncReadyCallback) _g_dbus_worker_do_read_cb, + _g_dbus_worker_ref (worker)); + else + { + worker->read_ancillary_messages = NULL; + worker->read_num_ancillary_messages = 0; + _g_socket_read_with_control_messages (worker->socket, + worker->read_buffer + worker->read_buffer_cur_size, + worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size, + &worker->read_ancillary_messages, + &worker->read_num_ancillary_messages, + G_PRIORITY_DEFAULT, + worker->cancellable, + (GAsyncReadyCallback) _g_dbus_worker_do_read_cb, + _g_dbus_worker_ref (worker)); + } } /* called in private thread shared by all GDBusConnection instances (without lock held) */ @@ -397,6 +636,9 @@ _g_dbus_worker_new (GIOStream *stream, worker->stream = g_object_ref (stream); worker->cancellable = g_cancellable_new (); + if (G_IS_SOCKET_CONNECTION (worker->stream)) + worker->socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)); + _g_dbus_shared_thread_ref (_g_dbus_worker_thread_begin_func, worker); return worker; diff --git a/gdbus/tests/peer.c b/gdbus/tests/peer.c index b1fa559..610bb31 100644 --- a/gdbus/tests/peer.c +++ b/gdbus/tests/peer.c @@ -24,6 +24,11 @@ #include <unistd.h> #include <string.h> +/* for open(2) */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + #include <gio/gunixsocketaddress.h> #include "tests.h" @@ -60,6 +65,11 @@ static const GDBusArgInfo test_interface_hello_peer_method_out_args[] = {"response", "s", NULL} }; +static const GDBusArgInfo test_interface_open_file_method_in_args[] = +{ + {"path", "s", NULL} +}; + static const GDBusMethodInfo test_interface_method_info[] = { { @@ -73,6 +83,12 @@ static const GDBusMethodInfo test_interface_method_info[] = "", 0, NULL, "", 0, NULL, NULL + }, + { + "OpenFile", + "s", 1, test_interface_open_file_method_in_args, + "", 0, NULL, + NULL } }; @@ -102,7 +118,7 @@ static const GDBusPropertyInfo test_interface_property_info[] = static const GDBusInterfaceInfo test_interface_introspection_data = { "org.gtk.GDBus.PeerTestInterface", - 2, test_interface_method_info, + 3, test_interface_method_info, 1, test_interface_signal_info, 1, test_interface_property_info, NULL, @@ -153,6 +169,37 @@ test_interface_method_call (GDBusConnection *connection, g_assert_no_error (error); g_dbus_method_invocation_return_value (invocation, NULL); } + else if (g_strcmp0 (method_name, "OpenFile") == 0) + { + const gchar *path; + GDBusMessage *reply; + GError *error; + gint fd; + GUnixFDList *fd_list; + + g_variant_get (parameters, "(s)", &path); + + fd_list = g_unix_fd_list_new (); + + error = NULL; + + fd = open (path, O_RDONLY); + g_unix_fd_list_append (fd_list, fd, &error); + g_assert_no_error (error); + close (fd); + + reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation)); + g_dbus_message_set_unix_fd_list (reply, fd_list); + g_object_unref (invocation); + + error = NULL; + g_dbus_connection_send_message (connection, + reply, + NULL, /* out_serial */ + &error); + g_assert_no_error (error); + g_object_unref (reply); + } else { g_assert_not_reached (); @@ -464,6 +511,59 @@ test_peer (void) g_assert (data.signal_received); g_assert_cmpint (data.num_method_calls, ==, 2); + /* check for UNIX fd passing */ +#ifdef G_OS_UNIX + { + GDBusMessage *method_call_message; + GDBusMessage *method_reply_message; + GUnixFDList *fd_list; + gint fd; + gchar buf[1024]; + gssize len; + gchar *buf2; + gsize len2; + + method_call_message = g_dbus_message_new_method_call (NULL, /* name */ + "/org/gtk/GDBus/PeerTestObject", + "org.gtk.GDBus.PeerTestInterface", + "OpenFile"); + g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", "/etc/hosts")); + error = NULL; + method_reply_message = g_dbus_connection_send_message_with_reply_sync (c, + method_call_message, + -1, + NULL, /* out_serial */ + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + g_assert (g_dbus_message_get_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN); + fd_list = g_dbus_message_get_unix_fd_list (method_reply_message); + g_assert (fd_list != NULL); + g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1); + error = NULL; + fd = g_unix_fd_list_get (fd_list, 0, &error); + g_assert_no_error (error); + g_object_unref (method_call_message); + g_object_unref (method_reply_message); + + memset (buf, '\0', sizeof (buf)); + len = read (fd, buf, sizeof (buf) - 1); + close (fd); + + error = NULL; + g_file_get_contents ("/etc/hosts", + &buf2, + &len2, + &error); + g_assert_no_error (error); + if (len2 > sizeof (buf)) + buf2[sizeof (buf)] = '\0'; + g_assert_cmpstr (buf, ==, buf2); + g_free (buf2); + } +#endif /* G_OS_UNIX */ + + /* bring up a connection - don't accept it - this should fail. */ data.accept_connection = FALSE; @@ -529,7 +629,7 @@ test_peer (void) g_variant_get (result, "(s)", &s); g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'."); g_variant_unref (result); - g_assert_cmpint (data.num_method_calls, ==, 3); + g_assert_cmpint (data.num_method_calls, ==, 4); #if 0 /* TODO: THIS TEST DOESN'T WORK YET */ |