/gio/gdbusprivate.c
C | 2234 lines | 1624 code | 311 blank | 299 comment | 217 complexity | c0d699e6a401d4298dd1c459906c3dc7 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0
Large files files are truncated, but you can click here to view the full file
- /* GDBus - GLib D-Bus Library
- *
- * Copyright (C) 2008-2010 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
- *
- * Author: David Zeuthen <davidz@redhat.com>
- */
- #include "config.h"
- #include <stdlib.h>
- #include <string.h>
- #include "giotypes.h"
- #include "gioenumtypes.h"
- #include "gsocket.h"
- #include "gdbusauthobserver.h"
- #include "gdbusprivate.h"
- #include "gdbusmessage.h"
- #include "gdbusconnection.h"
- #include "gdbusproxy.h"
- #include "gdbuserror.h"
- #include "gdbusintrospection.h"
- #include "gtask.h"
- #include "ginputstream.h"
- #include "gmemoryinputstream.h"
- #include "giostream.h"
- #include "glib/gstdio.h"
- #include "gsocketaddress.h"
- #include "gsocketcontrolmessage.h"
- #include "gsocketconnection.h"
- #include "gsocketoutputstream.h"
- #ifdef G_OS_UNIX
- #include "gunixfdmessage.h"
- #include "gunixconnection.h"
- #include "gunixcredentialsmessage.h"
- #endif
- #ifdef G_OS_WIN32
- #include <windows.h>
- #endif
- #include "glibintl.h"
- static gboolean _g_dbus_worker_do_initial_read (gpointer data);
- static void schedule_pending_close (GDBusWorker *worker);
- /* ---------------------------------------------------------------------------------------------------- */
- gchar *
- _g_dbus_hexdump (const gchar *data, gsize len, guint indent)
- {
- guint n, m;
- GString *ret;
- ret = g_string_new (NULL);
- for (n = 0; n < len; n += 16)
- {
- g_string_append_printf (ret, "%*s%04x: ", indent, "", n);
- for (m = n; m < n + 16; m++)
- {
- if (m > n && (m%4) == 0)
- g_string_append_c (ret, ' ');
- if (m < len)
- g_string_append_printf (ret, "%02x ", (guchar) data[m]);
- else
- g_string_append (ret, " ");
- }
- g_string_append (ret, " ");
- for (m = n; m < len && m < n + 16; m++)
- g_string_append_c (ret, g_ascii_isprint (data[m]) ? data[m] : '.');
- g_string_append_c (ret, '\n');
- }
- return g_string_free (ret, FALSE);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- /* 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
- {
- void *buffer;
- gsize count;
- GSocketControlMessage ***messages;
- gint *num_messages;
- } ReadWithControlData;
- static void
- read_with_control_data_free (ReadWithControlData *data)
- {
- g_slice_free (ReadWithControlData, data);
- }
- static gboolean
- _g_socket_read_with_control_messages_ready (GSocket *socket,
- GIOCondition condition,
- gpointer user_data)
- {
- GTask *task = user_data;
- ReadWithControlData *data = g_task_get_task_data (task);
- GError *error;
- gssize result;
- GInputVector vector;
- error = NULL;
- vector.buffer = data->buffer;
- vector.size = data->count;
- result = g_socket_receive_message (socket,
- NULL, /* address */
- &vector,
- 1,
- data->messages,
- data->num_messages,
- NULL,
- g_task_get_cancellable (task),
- &error);
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- {
- g_error_free (error);
- return TRUE;
- }
- g_assert (result >= 0 || error != NULL);
- if (result >= 0)
- g_task_return_int (task, result);
- else
- g_task_return_error (task, error);
- g_object_unref (task);
- 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)
- {
- GTask *task;
- ReadWithControlData *data;
- GSource *source;
- data = g_slice_new0 (ReadWithControlData);
- data->buffer = buffer;
- data->count = count;
- data->messages = messages;
- data->num_messages = num_messages;
- task = g_task_new (socket, cancellable, callback, user_data);
- g_task_set_source_tag (task, _g_socket_read_with_control_messages);
- g_task_set_task_data (task, data, (GDestroyNotify) read_with_control_data_free);
- if (g_socket_condition_check (socket, G_IO_IN))
- {
- if (!_g_socket_read_with_control_messages_ready (socket, G_IO_IN, task))
- return;
- }
- source = g_socket_create_source (socket,
- G_IO_IN | G_IO_HUP | G_IO_ERR,
- cancellable);
- g_task_attach_source (task, source, (GSourceFunc) _g_socket_read_with_control_messages_ready);
- g_source_unref (source);
- }
- static gssize
- _g_socket_read_with_control_messages_finish (GSocket *socket,
- GAsyncResult *result,
- GError **error)
- {
- g_return_val_if_fail (G_IS_SOCKET (socket), -1);
- g_return_val_if_fail (g_task_is_valid (result, socket), -1);
- return g_task_propagate_int (G_TASK (result), error);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- /* Work-around for https://bugzilla.gnome.org/show_bug.cgi?id=674885
- and see also the original https://bugzilla.gnome.org/show_bug.cgi?id=627724 */
- static GPtrArray *ensured_classes = NULL;
- static void
- ensure_type (GType gtype)
- {
- g_ptr_array_add (ensured_classes, g_type_class_ref (gtype));
- }
- static void
- release_required_types (void)
- {
- g_ptr_array_foreach (ensured_classes, (GFunc) g_type_class_unref, NULL);
- g_ptr_array_unref (ensured_classes);
- ensured_classes = NULL;
- }
- static void
- ensure_required_types (void)
- {
- g_assert (ensured_classes == NULL);
- ensured_classes = g_ptr_array_new ();
- /* Generally in this list, you should initialize types which are used as
- * properties first, then the class which has them. For example, GDBusProxy
- * has a type of GDBusConnection, so we initialize GDBusConnection first.
- * And because GDBusConnection has a property of type GDBusConnectionFlags,
- * we initialize that first.
- *
- * Similarly, GSocket has a type of GSocketAddress.
- *
- * We don't fill out the whole dependency tree right now because in practice
- * it tends to be just types that GDBus use that cause pain, and there
- * is work on a more general approach in https://bugzilla.gnome.org/show_bug.cgi?id=674885
- */
- ensure_type (G_TYPE_TASK);
- ensure_type (G_TYPE_MEMORY_INPUT_STREAM);
- ensure_type (G_TYPE_DBUS_CONNECTION_FLAGS);
- ensure_type (G_TYPE_DBUS_CAPABILITY_FLAGS);
- ensure_type (G_TYPE_DBUS_AUTH_OBSERVER);
- ensure_type (G_TYPE_DBUS_CONNECTION);
- ensure_type (G_TYPE_DBUS_PROXY);
- ensure_type (G_TYPE_SOCKET_FAMILY);
- ensure_type (G_TYPE_SOCKET_TYPE);
- ensure_type (G_TYPE_SOCKET_PROTOCOL);
- ensure_type (G_TYPE_SOCKET_ADDRESS);
- ensure_type (G_TYPE_SOCKET);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- typedef struct
- {
- volatile gint refcount;
- GThread *thread;
- GMainContext *context;
- GMainLoop *loop;
- } SharedThreadData;
- static gpointer
- gdbus_shared_thread_func (gpointer user_data)
- {
- SharedThreadData *data = user_data;
- g_main_context_push_thread_default (data->context);
- g_main_loop_run (data->loop);
- g_main_context_pop_thread_default (data->context);
- release_required_types ();
- return NULL;
- }
- /* ---------------------------------------------------------------------------------------------------- */
- static SharedThreadData *
- _g_dbus_shared_thread_ref (void)
- {
- static gsize shared_thread_data = 0;
- SharedThreadData *ret;
- if (g_once_init_enter (&shared_thread_data))
- {
- SharedThreadData *data;
- data = g_new0 (SharedThreadData, 1);
- data->refcount = 0;
-
- data->context = g_main_context_new ();
- data->loop = g_main_loop_new (data->context, FALSE);
- data->thread = g_thread_new ("gdbus",
- gdbus_shared_thread_func,
- data);
- /* We can cast between gsize and gpointer safely */
- g_once_init_leave (&shared_thread_data, (gsize) data);
- }
- ret = (SharedThreadData*) shared_thread_data;
- g_atomic_int_inc (&ret->refcount);
- return ret;
- }
- static void
- _g_dbus_shared_thread_unref (SharedThreadData *data)
- {
- /* TODO: actually destroy the shared thread here */
- #if 0
- g_assert (data != NULL);
- if (g_atomic_int_dec_and_test (&data->refcount))
- {
- g_main_loop_quit (data->loop);
- //g_thread_join (data->thread);
- g_main_loop_unref (data->loop);
- g_main_context_unref (data->context);
- }
- #endif
- }
- /* ---------------------------------------------------------------------------------------------------- */
- typedef enum {
- PENDING_NONE = 0,
- PENDING_WRITE,
- PENDING_FLUSH,
- PENDING_CLOSE
- } OutputPending;
- struct GDBusWorker
- {
- volatile gint ref_count;
- SharedThreadData *shared_thread_data;
- /* really a boolean, but GLib 2.28 lacks atomic boolean ops */
- volatile gint stopped;
- /* TODO: frozen (e.g. G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING) currently
- * only affects messages received from the other peer (since GDBusServer is the
- * only user) - we might want it to affect messages sent to the other peer too?
- */
- gboolean frozen;
- GDBusCapabilityFlags capabilities;
- GQueue *received_messages_while_frozen;
- GIOStream *stream;
- GCancellable *cancellable;
- GDBusWorkerMessageReceivedCallback message_received_callback;
- GDBusWorkerMessageAboutToBeSentCallback message_about_to_be_sent_callback;
- GDBusWorkerDisconnectedCallback disconnected_callback;
- gpointer user_data;
- /* if not NULL, stream is GSocketConnection */
- GSocket *socket;
- /* used for reading */
- GMutex read_lock;
- gchar *read_buffer;
- gsize read_buffer_allocated_size;
- gsize read_buffer_cur_size;
- gsize read_buffer_bytes_wanted;
- GUnixFDList *read_fd_list;
- GSocketControlMessage **read_ancillary_messages;
- gint read_num_ancillary_messages;
- /* Whether an async write, flush or close, or none of those, is pending.
- * Only the worker thread may change its value, and only with the write_lock.
- * Other threads may read its value when holding the write_lock.
- * The worker thread may read its value at any time.
- */
- OutputPending output_pending;
- /* used for writing */
- GMutex write_lock;
- /* queue of MessageToWriteData, protected by write_lock */
- GQueue *write_queue;
- /* protected by write_lock */
- guint64 write_num_messages_written;
- /* number of messages we'd written out last time we flushed;
- * protected by write_lock
- */
- guint64 write_num_messages_flushed;
- /* list of FlushData, protected by write_lock */
- GList *write_pending_flushes;
- /* list of CloseData, protected by write_lock */
- GList *pending_close_attempts;
- /* no lock - only used from the worker thread */
- gboolean close_expected;
- };
- static void _g_dbus_worker_unref (GDBusWorker *worker);
- /* ---------------------------------------------------------------------------------------------------- */
- typedef struct
- {
- GMutex mutex;
- GCond cond;
- guint64 number_to_wait_for;
- GError *error;
- } FlushData;
- struct _MessageToWriteData ;
- typedef struct _MessageToWriteData MessageToWriteData;
- static void message_to_write_data_free (MessageToWriteData *data);
- static void read_message_print_transport_debug (gssize bytes_read,
- GDBusWorker *worker);
- static void write_message_print_transport_debug (gssize bytes_written,
- MessageToWriteData *data);
- typedef struct {
- GDBusWorker *worker;
- GTask *task;
- } CloseData;
- static void close_data_free (CloseData *close_data)
- {
- g_clear_object (&close_data->task);
- _g_dbus_worker_unref (close_data->worker);
- g_slice_free (CloseData, close_data);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- static GDBusWorker *
- _g_dbus_worker_ref (GDBusWorker *worker)
- {
- g_atomic_int_inc (&worker->ref_count);
- return worker;
- }
- static void
- _g_dbus_worker_unref (GDBusWorker *worker)
- {
- if (g_atomic_int_dec_and_test (&worker->ref_count))
- {
- g_assert (worker->write_pending_flushes == NULL);
- _g_dbus_shared_thread_unref (worker->shared_thread_data);
- g_object_unref (worker->stream);
- g_mutex_clear (&worker->read_lock);
- g_object_unref (worker->cancellable);
- if (worker->read_fd_list != NULL)
- g_object_unref (worker->read_fd_list);
- g_queue_free_full (worker->received_messages_while_frozen, (GDestroyNotify) g_object_unref);
- g_mutex_clear (&worker->write_lock);
- g_queue_free_full (worker->write_queue, (GDestroyNotify) message_to_write_data_free);
- g_free (worker->read_buffer);
- g_free (worker);
- }
- }
- static void
- _g_dbus_worker_emit_disconnected (GDBusWorker *worker,
- gboolean remote_peer_vanished,
- GError *error)
- {
- if (!g_atomic_int_get (&worker->stopped))
- worker->disconnected_callback (worker, remote_peer_vanished, error, worker->user_data);
- }
- static void
- _g_dbus_worker_emit_message_received (GDBusWorker *worker,
- GDBusMessage *message)
- {
- if (!g_atomic_int_get (&worker->stopped))
- worker->message_received_callback (worker, message, worker->user_data);
- }
- static GDBusMessage *
- _g_dbus_worker_emit_message_about_to_be_sent (GDBusWorker *worker,
- GDBusMessage *message)
- {
- GDBusMessage *ret;
- if (!g_atomic_int_get (&worker->stopped))
- ret = worker->message_about_to_be_sent_callback (worker, message, worker->user_data);
- else
- ret = message;
- return ret;
- }
- /* can only be called from private thread with read-lock held - takes ownership of @message */
- static void
- _g_dbus_worker_queue_or_deliver_received_message (GDBusWorker *worker,
- GDBusMessage *message)
- {
- if (worker->frozen || g_queue_get_length (worker->received_messages_while_frozen) > 0)
- {
- /* queue up */
- g_queue_push_tail (worker->received_messages_while_frozen, message);
- }
- else
- {
- /* not frozen, nor anything in queue */
- _g_dbus_worker_emit_message_received (worker, message);
- g_object_unref (message);
- }
- }
- /* called in private thread shared by all GDBusConnection instances (without read-lock held) */
- static gboolean
- unfreeze_in_idle_cb (gpointer user_data)
- {
- GDBusWorker *worker = user_data;
- GDBusMessage *message;
- g_mutex_lock (&worker->read_lock);
- if (worker->frozen)
- {
- while ((message = g_queue_pop_head (worker->received_messages_while_frozen)) != NULL)
- {
- _g_dbus_worker_emit_message_received (worker, message);
- g_object_unref (message);
- }
- worker->frozen = FALSE;
- }
- else
- {
- g_assert (g_queue_get_length (worker->received_messages_while_frozen) == 0);
- }
- g_mutex_unlock (&worker->read_lock);
- return FALSE;
- }
- /* can be called from any thread */
- void
- _g_dbus_worker_unfreeze (GDBusWorker *worker)
- {
- GSource *idle_source;
- idle_source = g_idle_source_new ();
- g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
- g_source_set_callback (idle_source,
- unfreeze_in_idle_cb,
- _g_dbus_worker_ref (worker),
- (GDestroyNotify) _g_dbus_worker_unref);
- g_source_set_name (idle_source, "[gio] unfreeze_in_idle_cb");
- g_source_attach (idle_source, worker->shared_thread_data->context);
- g_source_unref (idle_source);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- static void _g_dbus_worker_do_read_unlocked (GDBusWorker *worker);
- /* called in private thread shared by all GDBusConnection instances (without read-lock held) */
- static void
- _g_dbus_worker_do_read_cb (GInputStream *input_stream,
- GAsyncResult *res,
- gpointer user_data)
- {
- GDBusWorker *worker = user_data;
- GError *error;
- gssize bytes_read;
- g_mutex_lock (&worker->read_lock);
- /* If already stopped, don't even process the reply */
- if (g_atomic_int_get (&worker->stopped))
- goto out;
- error = NULL;
- 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->read_fd_list == NULL)
- {
- worker->read_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->read_fd_list, fds[n], NULL);
- (void) g_close (fds[n], NULL);
- }
- }
- g_free (fds);
- }
- else if (G_IS_UNIX_CREDENTIALS_MESSAGE (control_message))
- {
- /* do nothing */
- }
- #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)
- {
- if (G_UNLIKELY (_g_dbus_debug_transport ()))
- {
- _g_dbus_debug_print_lock ();
- g_print ("========================================================================\n"
- "GDBus-debug:Transport:\n"
- " ---- READ ERROR on stream of type %s:\n"
- " ---- %s %d: %s\n",
- g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_input_stream (worker->stream))),
- g_quark_to_string (error->domain), error->code,
- error->message);
- _g_dbus_debug_print_unlock ();
- }
- /* Every async read that uses this callback uses worker->cancellable
- * as its GCancellable. worker->cancellable gets cancelled if and only
- * if the GDBusConnection tells us to close (either via
- * _g_dbus_worker_stop, which is called on last-unref, or directly),
- * so a cancelled read must mean our connection was closed locally.
- *
- * If we're closing, other errors are possible - notably,
- * G_IO_ERROR_CLOSED can be seen if we close the stream with an async
- * read in-flight. It seems sensible to treat all read errors during
- * closing as an expected thing that doesn't trip exit-on-close.
- *
- * Because close_expected can't be set until we get into the worker
- * thread, but the cancellable is signalled sooner (from another
- * thread), we do still need to check the error.
- */
- if (worker->close_expected ||
- g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- _g_dbus_worker_emit_disconnected (worker, FALSE, NULL);
- else
- _g_dbus_worker_emit_disconnected (worker, TRUE, error);
- g_error_free (error);
- goto out;
- }
- #if 0
- g_debug ("read %d bytes (is_closed=%d blocking=%d condition=0x%02x) stream %p, %p",
- (gint) bytes_read,
- g_socket_is_closed (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
- g_socket_get_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
- g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)),
- G_IO_IN | G_IO_OUT | G_IO_HUP),
- worker->stream,
- worker);
- #endif
- /* TODO: hmm, hmm... */
- if (bytes_read == 0)
- {
- g_set_error (&error,
- G_IO_ERROR,
- G_IO_ERROR_FAILED,
- "Underlying GIOStream returned 0 bytes on an async read");
- _g_dbus_worker_emit_disconnected (worker, TRUE, error);
- g_error_free (error);
- goto out;
- }
- read_message_print_transport_debug (bytes_read, worker);
- worker->read_buffer_cur_size += bytes_read;
- if (worker->read_buffer_bytes_wanted == worker->read_buffer_cur_size)
- {
- /* OK, got what we asked for! */
- if (worker->read_buffer_bytes_wanted == 16)
- {
- gssize message_len;
- /* OK, got the header - determine how many more bytes are needed */
- error = NULL;
- message_len = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer,
- 16,
- &error);
- if (message_len == -1)
- {
- g_warning ("_g_dbus_worker_do_read_cb: error determining bytes needed: %s", error->message);
- _g_dbus_worker_emit_disconnected (worker, FALSE, error);
- g_error_free (error);
- goto out;
- }
- worker->read_buffer_bytes_wanted = message_len;
- _g_dbus_worker_do_read_unlocked (worker);
- }
- else
- {
- GDBusMessage *message;
- error = NULL;
- /* TODO: use connection->priv->auth to decode the message */
- message = g_dbus_message_new_from_blob ((guchar *) worker->read_buffer,
- worker->read_buffer_cur_size,
- worker->capabilities,
- &error);
- if (message == NULL)
- {
- gchar *s;
- s = _g_dbus_hexdump (worker->read_buffer, worker->read_buffer_cur_size, 2);
- g_warning ("Error decoding D-Bus message of %" G_GSIZE_FORMAT " bytes\n"
- "The error is: %s\n"
- "The payload is as follows:\n"
- "%s\n",
- worker->read_buffer_cur_size,
- error->message,
- s);
- g_free (s);
- _g_dbus_worker_emit_disconnected (worker, FALSE, error);
- g_error_free (error);
- goto out;
- }
- #ifdef G_OS_UNIX
- if (worker->read_fd_list != NULL)
- {
- g_dbus_message_set_unix_fd_list (message, worker->read_fd_list);
- g_object_unref (worker->read_fd_list);
- worker->read_fd_list = NULL;
- }
- #endif
- if (G_UNLIKELY (_g_dbus_debug_message ()))
- {
- gchar *s;
- _g_dbus_debug_print_lock ();
- g_print ("========================================================================\n"
- "GDBus-debug:Message:\n"
- " <<<< RECEIVED D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
- worker->read_buffer_cur_size);
- s = g_dbus_message_print (message, 2);
- g_print ("%s", s);
- g_free (s);
- if (G_UNLIKELY (_g_dbus_debug_payload ()))
- {
- s = _g_dbus_hexdump (worker->read_buffer, worker->read_buffer_cur_size, 2);
- g_print ("%s\n", s);
- g_free (s);
- }
- _g_dbus_debug_print_unlock ();
- }
- /* yay, got a message, go deliver it */
- _g_dbus_worker_queue_or_deliver_received_message (worker, message);
- /* start reading another message! */
- worker->read_buffer_bytes_wanted = 0;
- worker->read_buffer_cur_size = 0;
- _g_dbus_worker_do_read_unlocked (worker);
- }
- }
- else
- {
- /* didn't get all the bytes we requested - so repeat the request... */
- _g_dbus_worker_do_read_unlocked (worker);
- }
- out:
- g_mutex_unlock (&worker->read_lock);
- /* gives up the reference acquired when calling g_input_stream_read_async() */
- _g_dbus_worker_unref (worker);
- /* check if there is any pending close */
- schedule_pending_close (worker);
- }
- /* called in private thread shared by all GDBusConnection instances (with read-lock held) */
- static void
- _g_dbus_worker_do_read_unlocked (GDBusWorker *worker)
- {
- /* Note that we do need to keep trying to read even if close_expected is
- * true, because only failing a read causes us to signal 'closed'.
- */
- /* if bytes_wanted is zero, it means start reading a message */
- if (worker->read_buffer_bytes_wanted == 0)
- {
- worker->read_buffer_cur_size = 0;
- worker->read_buffer_bytes_wanted = 16;
- }
- /* ensure we have a (big enough) buffer */
- if (worker->read_buffer == NULL || worker->read_buffer_bytes_wanted > worker->read_buffer_allocated_size)
- {
- /* TODO: 4096 is randomly chosen; might want a better chosen default minimum */
- worker->read_buffer_allocated_size = MAX (worker->read_buffer_bytes_wanted, 4096);
- worker->read_buffer = g_realloc (worker->read_buffer, worker->read_buffer_allocated_size);
- }
- 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 read-lock held) */
- static gboolean
- _g_dbus_worker_do_initial_read (gpointer data)
- {
- GDBusWorker *worker = data;
- g_mutex_lock (&worker->read_lock);
- _g_dbus_worker_do_read_unlocked (worker);
- g_mutex_unlock (&worker->read_lock);
- return FALSE;
- }
- /* ---------------------------------------------------------------------------------------------------- */
- struct _MessageToWriteData
- {
- GDBusWorker *worker;
- GDBusMessage *message;
- gchar *blob;
- gsize blob_size;
- gsize total_written;
- GTask *task;
- };
- static void
- message_to_write_data_free (MessageToWriteData *data)
- {
- _g_dbus_worker_unref (data->worker);
- if (data->message)
- g_object_unref (data->message);
- g_free (data->blob);
- g_slice_free (MessageToWriteData, data);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- static void write_message_continue_writing (MessageToWriteData *data);
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_WRITE on entry
- */
- static void
- write_message_async_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
- {
- MessageToWriteData *data = user_data;
- GTask *task;
- gssize bytes_written;
- GError *error;
- /* Note: we can't access data->task after calling g_task_return_* () because the
- * callback can free @data and we're not completing in idle. So use a copy of the pointer.
- */
- task = data->task;
- error = NULL;
- bytes_written = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object),
- res,
- &error);
- if (bytes_written == -1)
- {
- g_task_return_error (task, error);
- g_object_unref (task);
- goto out;
- }
- g_assert (bytes_written > 0); /* zero is never returned */
- write_message_print_transport_debug (bytes_written, data);
- data->total_written += bytes_written;
- g_assert (data->total_written <= data->blob_size);
- if (data->total_written == data->blob_size)
- {
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
- goto out;
- }
- write_message_continue_writing (data);
- out:
- ;
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_WRITE on entry
- */
- #ifdef G_OS_UNIX
- static gboolean
- on_socket_ready (GSocket *socket,
- GIOCondition condition,
- gpointer user_data)
- {
- MessageToWriteData *data = user_data;
- write_message_continue_writing (data);
- return FALSE; /* remove source */
- }
- #endif
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_WRITE on entry
- */
- static void
- write_message_continue_writing (MessageToWriteData *data)
- {
- GOutputStream *ostream;
- #ifdef G_OS_UNIX
- GTask *task;
- GUnixFDList *fd_list;
- #endif
- #ifdef G_OS_UNIX
- /* Note: we can't access data->task after calling g_task_return_* () because the
- * callback can free @data and we're not completing in idle. So use a copy of the pointer.
- */
- task = data->task;
- #endif
- ostream = g_io_stream_get_output_stream (data->worker->stream);
- #ifdef G_OS_UNIX
- fd_list = g_dbus_message_get_unix_fd_list (data->message);
- #endif
- g_assert (!g_output_stream_has_pending (ostream));
- g_assert_cmpint (data->total_written, <, data->blob_size);
- if (FALSE)
- {
- }
- #ifdef G_OS_UNIX
- else if (G_IS_SOCKET_OUTPUT_STREAM (ostream) && data->total_written == 0)
- {
- GOutputVector vector;
- GSocketControlMessage *control_message;
- gssize bytes_written;
- GError *error;
- vector.buffer = data->blob;
- vector.size = data->blob_size;
- control_message = NULL;
- if (fd_list != NULL && g_unix_fd_list_get_length (fd_list) > 0)
- {
- if (!(data->worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING))
- {
- g_task_return_new_error (task,
- G_IO_ERROR,
- G_IO_ERROR_FAILED,
- "Tried sending a file descriptor but remote peer does not support this capability");
- g_object_unref (task);
- goto out;
- }
- control_message = g_unix_fd_message_new_with_fd_list (fd_list);
- }
- error = NULL;
- bytes_written = g_socket_send_message (data->worker->socket,
- NULL, /* address */
- &vector,
- 1,
- control_message != NULL ? &control_message : NULL,
- control_message != NULL ? 1 : 0,
- G_SOCKET_MSG_NONE,
- data->worker->cancellable,
- &error);
- if (control_message != NULL)
- g_object_unref (control_message);
- if (bytes_written == -1)
- {
- /* Handle WOULD_BLOCK by waiting until there's room in the buffer */
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- {
- GSource *source;
- source = g_socket_create_source (data->worker->socket,
- G_IO_OUT | G_IO_HUP | G_IO_ERR,
- data->worker->cancellable);
- g_source_set_callback (source,
- (GSourceFunc) on_socket_ready,
- data,
- NULL); /* GDestroyNotify */
- g_source_attach (source, g_main_context_get_thread_default ());
- g_source_unref (source);
- g_error_free (error);
- goto out;
- }
- g_task_return_error (task, error);
- g_object_unref (task);
- goto out;
- }
- g_assert (bytes_written > 0); /* zero is never returned */
- write_message_print_transport_debug (bytes_written, data);
- data->total_written += bytes_written;
- g_assert (data->total_written <= data->blob_size);
- if (data->total_written == data->blob_size)
- {
- g_task_return_boolean (task, TRUE);
- g_object_unref (task);
- goto out;
- }
- write_message_continue_writing (data);
- }
- #endif
- else
- {
- #ifdef G_OS_UNIX
- if (fd_list != NULL)
- {
- g_task_return_new_error (task,
- G_IO_ERROR,
- G_IO_ERROR_FAILED,
- "Tried sending a file descriptor on unsupported stream of type %s",
- g_type_name (G_TYPE_FROM_INSTANCE (ostream)));
- g_object_unref (task);
- goto out;
- }
- #endif
- g_output_stream_write_async (ostream,
- (const gchar *) data->blob + data->total_written,
- data->blob_size - data->total_written,
- G_PRIORITY_DEFAULT,
- data->worker->cancellable,
- write_message_async_cb,
- data);
- }
- #ifdef G_OS_UNIX
- out:
- #endif
- ;
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_WRITE on entry
- */
- static void
- write_message_async (GDBusWorker *worker,
- MessageToWriteData *data,
- GAsyncReadyCallback callback,
- gpointer user_data)
- {
- data->task = g_task_new (NULL, NULL, callback, user_data);
- g_task_set_source_tag (data->task, write_message_async);
- data->total_written = 0;
- write_message_continue_writing (data);
- }
- /* called in private thread shared by all GDBusConnection instances (with write-lock held) */
- static gboolean
- write_message_finish (GAsyncResult *res,
- GError **error)
- {
- g_return_val_if_fail (g_task_is_valid (res, NULL), FALSE);
- return g_task_propagate_boolean (G_TASK (res), error);
- }
- /* ---------------------------------------------------------------------------------------------------- */
- static void continue_writing (GDBusWorker *worker);
- typedef struct
- {
- GDBusWorker *worker;
- GList *flushers;
- } FlushAsyncData;
- static void
- flush_data_list_complete (const GList *flushers,
- const GError *error)
- {
- const GList *l;
- for (l = flushers; l != NULL; l = l->next)
- {
- FlushData *f = l->data;
- f->error = error != NULL ? g_error_copy (error) : NULL;
- g_mutex_lock (&f->mutex);
- g_cond_signal (&f->cond);
- g_mutex_unlock (&f->mutex);
- }
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_FLUSH on entry
- */
- static void
- ostream_flush_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
- {
- FlushAsyncData *data = user_data;
- GError *error;
- error = NULL;
- g_output_stream_flush_finish (G_OUTPUT_STREAM (source_object),
- res,
- &error);
- if (error == NULL)
- {
- if (G_UNLIKELY (_g_dbus_debug_transport ()))
- {
- _g_dbus_debug_print_lock ();
- g_print ("========================================================================\n"
- "GDBus-debug:Transport:\n"
- " ---- FLUSHED stream of type %s\n",
- g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_output_stream (data->worker->stream))));
- _g_dbus_debug_print_unlock ();
- }
- }
- g_assert (data->flushers != NULL);
- flush_data_list_complete (data->flushers, error);
- g_list_free (data->flushers);
- if (error != NULL)
- g_error_free (error);
- /* Make sure we tell folks that we don't have additional
- flushes pending */
- g_mutex_lock (&data->worker->write_lock);
- data->worker->write_num_messages_flushed = data->worker->write_num_messages_written;
- g_assert (data->worker->output_pending == PENDING_FLUSH);
- data->worker->output_pending = PENDING_NONE;
- g_mutex_unlock (&data->worker->write_lock);
- /* OK, cool, finally kick off the next write */
- continue_writing (data->worker);
- _g_dbus_worker_unref (data->worker);
- g_free (data);
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_FLUSH on entry
- */
- static void
- start_flush (FlushAsyncData *data)
- {
- g_output_stream_flush_async (g_io_stream_get_output_stream (data->worker->stream),
- G_PRIORITY_DEFAULT,
- data->worker->cancellable,
- ostream_flush_cb,
- data);
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is held on entry
- * output_pending is PENDING_NONE on entry
- */
- static void
- message_written_unlocked (GDBusWorker *worker,
- MessageToWriteData *message_data)
- {
- if (G_UNLIKELY (_g_dbus_debug_message ()))
- {
- gchar *s;
- _g_dbus_debug_print_lock ();
- g_print ("========================================================================\n"
- "GDBus-debug:Message:\n"
- " >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
- message_data->blob_size);
- s = g_dbus_message_print (message_data->message, 2);
- g_print ("%s", s);
- g_free (s);
- if (G_UNLIKELY (_g_dbus_debug_payload ()))
- {
- s = _g_dbus_hexdump (message_data->blob, message_data->blob_size, 2);
- g_print ("%s\n", s);
- g_free (s);
- }
- _g_dbus_debug_print_unlock ();
- }
- worker->write_num_messages_written += 1;
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is held on entry
- * output_pending is PENDING_NONE on entry
- *
- * Returns: non-%NULL, setting @output_pending, if we need to flush now
- */
- static FlushAsyncData *
- prepare_flush_unlocked (GDBusWorker *worker)
- {
- GList *l;
- GList *ll;
- GList *flushers;
- flushers = NULL;
- for (l = worker->write_pending_flushes; l != NULL; l = ll)
- {
- FlushData *f = l->data;
- ll = l->next;
- if (f->number_to_wait_for == worker->write_num_messages_written)
- {
- flushers = g_list_append (flushers, f);
- worker->write_pending_flushes = g_list_delete_link (worker->write_pending_flushes, l);
- }
- }
- if (flushers != NULL)
- {
- g_assert (worker->output_pending == PENDING_NONE);
- worker->output_pending = PENDING_FLUSH;
- }
- if (flushers != NULL)
- {
- FlushAsyncData *data;
- data = g_new0 (FlushAsyncData, 1);
- data->worker = _g_dbus_worker_ref (worker);
- data->flushers = flushers;
- return data;
- }
- return NULL;
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_WRITE on entry
- */
- static void
- write_message_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
- {
- MessageToWriteData *data = user_data;
- GError *error;
- g_mutex_lock (&data->worker->write_lock);
- g_assert (data->worker->output_pending == PENDING_WRITE);
- data->worker->output_pending = PENDING_NONE;
- error = NULL;
- if (!write_message_finish (res, &error))
- {
- g_mutex_unlock (&data->worker->write_lock);
- /* TODO: handle */
- _g_dbus_worker_emit_disconnected (data->worker, TRUE, error);
- g_error_free (error);
- g_mutex_lock (&data->worker->write_lock);
- }
- message_written_unlocked (data->worker, data);
- g_mutex_unlock (&data->worker->write_lock);
- continue_writing (data->worker);
- message_to_write_data_free (data);
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending is PENDING_CLOSE on entry
- */
- static void
- iostream_close_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
- {
- GDBusWorker *worker = user_data;
- GError *error = NULL;
- GList *pending_close_attempts, *pending_flush_attempts;
- GQueue *send_queue;
- g_io_stream_close_finish (worker->stream, res, &error);
- g_mutex_lock (&worker->write_lock);
- pending_close_attempts = worker->pending_close_attempts;
- worker->pending_close_attempts = NULL;
- pending_flush_attempts = worker->write_pending_flushes;
- worker->write_pending_flushes = NULL;
- send_queue = worker->write_queue;
- worker->write_queue = g_queue_new ();
- g_assert (worker->output_pending == PENDING_CLOSE);
- worker->output_pending = PENDING_NONE;
- g_mutex_unlock (&worker->write_lock);
- while (pending_close_attempts != NULL)
- {
- CloseData *close_data = pending_close_attempts->data;
- pending_close_attempts = g_list_delete_link (pending_close_attempts,
- pending_close_attempts);
- if (close_data->task != NULL)
- {
- if (error != NULL)
- g_task_return_error (close_data->task, g_error_copy (error));
- else
- g_task_return_boolean (close_data->task, TRUE);
- }
- close_data_free (close_data);
- }
- g_clear_error (&error);
- /* all messages queued for sending are discarded */
- g_queue_free_full (send_queue, (GDestroyNotify) message_to_write_data_free);
- /* all queued flushes fail */
- error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED,
- _("Operation was cancelled"));
- flush_data_list_complete (pending_flush_attempts, error);
- g_list_free (pending_flush_attempts);
- g_clear_error (&error);
- _g_dbus_worker_unref (worker);
- }
- /* called in private thread shared by all GDBusConnection instances
- *
- * write-lock is not held on entry
- * output_pending must be PENDING_NONE on entry
- */
- static void
- continue_writing (GDBusWorker *worker)
- {
- MessageToWriteData *data;
- FlushAsyncData *flush_async_data;
- write_next:
- /* we mustn't try to write two things at once */
- g_assert (worker->output_pending == PENDING_NONE);
- g_mutex_lock (&worker->write_lock);
- data = NULL;
- flush_async_data = NULL;
- /* if we want to close the connection, that takes precedence */
- if (worker->pending_close_attempts != NULL)
- {
- GInputStream *input = g_io_stream_get_input_stream (worker->stream);
- if (!g_input_stream_has_pending (input))
- {
- worker->close_expected = TRUE;
- worker->output_pending = PENDING_CLOSE;
- g_io_stream_close_async (worker->stream, G_PRIORITY_DEFAULT,
- NULL, iostream_close_cb,
- _g_dbus_worker_ref (worker));
- }
- }
- else
- {
- flush_async_data = prepare_flush_unlocked (worker);
- if (flush_async_data == NULL)
- {
- data = g_queue_pop_head (worker->write_queue);
- if (data != NULL)
- worker->output_pending = PENDING_WRITE;
- }
- }
- g_mutex_unlock (&worker->write_lock);
- /* Note that write_lock is only used for protecting the @write_queue
- * and @output_pending fields of the GDBusWorker struct ... which we
- * need to modify from arbitrary threads in _g_dbus_worker_send_message().
- *
- * Therefore, it's fine to drop it here when calling back into user
- * code and then writing the message out onto the GIOStream since this
- * function only runs on the worker thread.
- */
- if (flush_async_data != NULL)
- {
- start_flush (flush_async_data);
- g_assert (data == NULL);
- }
- else if (data != NULL)
- {
- GDBusMessage *old_message;
- guchar *new_blob;
- gsize new_blob_size;
- GError *error;
- old_message = data->message;
- data->message = _g_dbus_worker_emit_message_about_to_be_sent (worker, data->message);
- if (data->message == old_message)
- {
- /* filters had no effect - do nothing */
- }
- else if (data->message == NULL)
- {
- /* filters dropped message */
- g_mutex_lock (&worker->write_lock);
- worker->output_pending = PENDING_NONE;
- g_mutex_unlock (&worker->write_lock);
- message_to_write_data_free (data);
- goto write_next;
- }
- else
- {
- /* filters altered the message -> reencode */
- error = NULL;
- new_blob = g_dbus_message_to_blob (data->message,
- &new_blob_size,
- worker->capabilities,
- &error);
- if (new_blob == NULL)
- {
- /* if filter make the GDBusMessage unencodeable, just complain on stderr and send
- * the old message instead
- */
- g_warning ("Error encoding GDBusMessage with serial %d altered by filter function: %s",
- g_dbus_message_get_serial (data->message),
- error->message);
- g_error_free (error);
- }
- else
- {
- g_free (data->blob);
- data->blob = (gchar *) new_blob;
- …
Large files files are truncated, but you can click here to view the full file