| /* GDBus - GLib D-Bus Library | |
| * | |
| * Copyright (C) 2008-2010 Red Hat, Inc. | |
| * | |
| * SPDX-License-Identifier: LGPL-2.1-or-later | |
| * | |
| * 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 "gdbusauthobserver.h" | |
| #include "gdbusconnection.h" | |
| #include "gdbusdaemon.h" | |
| #include "gdbuserror.h" | |
| #include "gdbusintrospection.h" | |
| #include "gdbusmessage.h" | |
| #include "gdbusprivate.h" | |
| #include "gdbusproxy.h" | |
| #include "ginputstream.h" | |
| #include "gioenumtypes.h" | |
| #include "giomodule-priv.h" | |
| #include "giostream.h" | |
| #include "giotypes.h" | |
| #include "glib-private.h" | |
| #include "glib/gstdio.h" | |
| #include "gmemoryinputstream.h" | |
| #include "gsocket.h" | |
| #include "gsocketaddress.h" | |
| #include "gsocketconnection.h" | |
| #include "gsocketcontrolmessage.h" | |
| #include "gsocketoutputstream.h" | |
| #include "gtask.h" | |
| #ifdef G_OS_UNIX | |
| #include "gunixfdmessage.h" | |
| #include "gunixconnection.h" | |
| #include "gunixcredentialsmessage.h" | |
| #endif | |
| #ifdef G_OS_WIN32 | |
| #include <windows.h> | |
| #include <io.h> | |
| #include <conio.h> | |
| #include "gwin32sid.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_name (task, "[gio] D-Bus read"); | |
| 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 | |
| { | |
| gint refcount; /* (atomic) */ | |
| 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 SharedThreadData *shared_thread_data = 0; | |
| if (g_once_init_enter_pointer (&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_pointer (&shared_thread_data, data); | |
| } | |
| g_atomic_int_inc (&shared_thread_data->refcount); | |
| return shared_thread_data; | |
| } | |
| 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 | |
| { | |
| gint ref_count; /* (atomic) */ | |
| SharedThreadData *shared_thread_data; | |
| /* really a boolean, but GLib 2.28 lacks atomic boolean ops */ | |
| gint stopped; /* (atomic) */ | |
| /* 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; | |
| gboolean finished; | |
| 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, g_steal_pointer (&message), worker->user_data); | |
| else | |
| ret = g_steal_pointer (&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, g_steal_pointer (&message)); | |
| } | |
| else | |
| { | |
| /* not frozen, nor anything in queue */ | |
| _g_dbus_worker_emit_message_received (worker, message); | |
| g_clear_object (&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_clear_object (&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_static_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 | |
| /* The read failed, which could mean the dbus-daemon was sent SIGTERM. */ | |
| 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", | |
| 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, g_steal_pointer (&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); | |
| /* check if there is any pending close */ | |
| schedule_pending_close (worker); | |
| /* gives up the reference acquired when calling g_input_stream_read_async() */ | |
| _g_dbus_worker_unref (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; /* (owned) */ | |
| gchar *blob; | |
| gsize blob_size; | |
| gsize total_written; | |
| GTask *task; /* (owned) and (nullable) before writing starts and after g_task_return_*() is called */ | |
| }; | |
| static void | |
| message_to_write_data_free (MessageToWriteData *data) | |
| { | |
| _g_dbus_worker_unref (data->worker); | |
| g_clear_object (&data->message); | |
| g_free (data->blob); | |
| /* The task must either not have been created, or have been created, returned | |
| * and finalised by now. */ | |
| g_assert (data->task == NULL); | |
| 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 | |
| * @user_data is (transfer full) | |
| */ | |
| static void | |
| write_message_async_cb (GObject *source_object, | |
| GAsyncResult *res, | |
| gpointer user_data) | |
| { | |
| MessageToWriteData *data = g_steal_pointer (&user_data); | |
| gssize bytes_written; | |
| GError *error; | |
| /* The ownership of @data is a bit odd in this function: it’s (transfer full) | |
| * when the function is called, but the code paths which call g_task_return_*() | |
| * on @data->task will indirectly cause it to be freed, because @data is | |
| * always guaranteed to be the user_data in the #GTask. So that’s why it looks | |
| * like @data is not always freed on every code path in this function. */ | |
| error = NULL; | |
| bytes_written = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), | |
| res, | |
| &error); | |
| if (bytes_written == -1) | |
| { | |
| GTask *task = g_steal_pointer (&data->task); | |
| g_task_return_error (task, error); | |
| g_clear_object (&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) | |
| { | |
| GTask *task = g_steal_pointer (&data->task); | |
| g_task_return_boolean (task, TRUE); | |
| g_clear_object (&task); | |
| goto out; | |
| } | |
| write_message_continue_writing (g_steal_pointer (&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 = g_steal_pointer (&user_data); | |
| write_message_continue_writing (g_steal_pointer (&data)); | |
| return G_SOURCE_REMOVE; | |
| } | |
| #endif | |
| /* called in private thread shared by all GDBusConnection instances | |
| * | |
| * write-lock is not held on entry | |
| * output_pending is PENDING_WRITE on entry | |
| * @data is (transfer full) | |
| */ | |
| static void | |
| write_message_continue_writing (MessageToWriteData *data) | |
| { | |
| GOutputStream *ostream; | |
| #ifdef G_OS_UNIX | |
| GUnixFDList *fd_list; | |
| #endif | |
| /* The ownership of @data is a bit odd in this function: it’s (transfer full) | |
| * when the function is called, but the code paths which call g_task_return_*() | |
| * on @data->task will indirectly cause it to be freed, because @data is | |
| * always guaranteed to be the user_data in the #GTask. So that’s why it looks | |
| * like @data is not always freed on every code path in this function. */ | |
| 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)) | |
| { | |
| GTask *task = g_steal_pointer (&data->task); | |
| g_task_return_new_error_literal (task, | |
| G_IO_ERROR, | |
| G_IO_ERROR_FAILED, | |
| "Tried sending a file descriptor " | |
| "but remote peer does not support " | |
| "this capability"); | |
| g_clear_object (&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, | |
| g_steal_pointer (&data), | |
| NULL); /* GDestroyNotify */ | |
| g_source_attach (source, g_main_context_get_thread_default ()); | |
| g_source_unref (source); | |
| g_error_free (error); | |
| goto out; | |
| } | |
| else | |
| { | |
| GTask *task = g_steal_pointer (&data->task); | |
| g_task_return_error (task, error); | |
| g_clear_object (&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) | |
| { | |
| GTask *task = g_steal_pointer (&data->task); | |
| g_task_return_boolean (task, TRUE); | |
| g_clear_object (&task); | |
| goto out; | |
| } | |
| write_message_continue_writing (g_steal_pointer (&data)); | |
| } | |
| #endif | |
| else | |
| { | |
| #ifdef G_OS_UNIX | |
| if (data->total_written == 0 && fd_list != NULL) | |
| { | |
| /* We were trying to write byte 0 of the message, which needs | |
| * the fd list to be attached to it, but this connection doesn't | |
| * support doing that. */ | |
| GTask *task = g_steal_pointer (&data->task); | |
| 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_clear_object (&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); /* steal @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); | |
| g_task_set_name (data->task, "[gio] D-Bus write message"); | |
| data->total_written = 0; | |
| write_message_continue_writing (g_steal_pointer (&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); | |
| f->finished = TRUE; | |
| 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 (); | |
| } | |
| } | |
| /* 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); | |
| g_assert (data->flushers != NULL); | |
| flush_data_list_complete (data->flushers, error); | |
| g_list_free (data->flushers); | |
| if (error != NULL) | |
| g_error_free (error); | |
| /* 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 | |
| * @user_data is (transfer full) | |
| */ | |
| 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; | |
| /* Ensure threads waiting for pending flushes to finish will be unblocked. */ | |
| worker->write_num_messages_flushed = | |
| worker->write_num_messages_written + g_list_length(pending_flush_attempts); | |
| 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 -> re-encode */ | |
| 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; | |
| data->blob_size = new_blob_size; | |
| } | |
| } | |
| write_message_async (worker, | |
| data, | |
| write_message_cb, | |
| data); /* takes ownership of @data as user_data */ | |
| } | |
| } | |
| /* called in private thread shared by all GDBusConnection instances | |
| * | |
| * write-lock is not held on entry | |
| * output_pending may be anything | |
| */ | |
| static gboolean | |
| continue_writing_in_idle_cb (gpointer user_data) | |
| { | |
| GDBusWorker *worker = user_data; | |
| /* Because this is the worker thread, we can read this struct member | |
| * without holding the lock: no other thread ever modifies it. | |
| */ | |
| if (worker->output_pending == PENDING_NONE) | |
| continue_writing (worker); | |
| return FALSE; | |
| } | |
| /* | |
| * @write_data: (transfer full) (nullable): | |
| * @flush_data: (transfer full) (nullable): | |
| * @close_data: (transfer full) (nullable): | |
| * | |
| * Can be called from any thread | |
| * | |
| * write_lock is held on entry | |
| * output_pending may be anything | |
| */ | |
| static void | |
| schedule_writing_unlocked (GDBusWorker *worker, | |
| MessageToWriteData *write_data, | |
| FlushData *flush_data, | |
| CloseData *close_data) | |
| { | |
| if (write_data != NULL) | |
| g_queue_push_tail (worker->write_queue, write_data); | |
| if (flush_data != NULL) | |
| worker->write_pending_flushes = g_list_prepend (worker->write_pending_flushes, flush_data); | |
| if (close_data != NULL) | |
| worker->pending_close_attempts = g_list_prepend (worker->pending_close_attempts, | |
| close_data); | |
| /* If we had output pending, the next bit of output will happen | |
| * automatically when it finishes, so we only need to do this | |
| * if nothing was pending. | |
| * | |
| * The idle callback will re-check that output_pending is still | |
| * PENDING_NONE, to guard against output starting before the idle. | |
| */ | |
| if (worker->output_pending == PENDING_NONE) | |
| { | |
| GSource *idle_source; | |
| idle_source = g_idle_source_new (); | |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); | |
| g_source_set_callback (idle_source, | |
| continue_writing_in_idle_cb, | |
| _g_dbus_worker_ref (worker), | |
| (GDestroyNotify) _g_dbus_worker_unref); | |
| g_source_set_static_name (idle_source, "[gio] continue_writing_in_idle_cb"); | |
| g_source_attach (idle_source, worker->shared_thread_data->context); | |
| g_source_unref (idle_source); | |
| } | |
| } | |
| static void | |
| schedule_pending_close (GDBusWorker *worker) | |
| { | |
| g_mutex_lock (&worker->write_lock); | |
| if (worker->pending_close_attempts) | |
| schedule_writing_unlocked (worker, NULL, NULL, NULL); | |
| g_mutex_unlock (&worker->write_lock); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* can be called from any thread - steals blob | |
| * | |
| * write_lock is not held on entry | |
| * output_pending may be anything | |
| */ | |
| void | |
| _g_dbus_worker_send_message (GDBusWorker *worker, | |
| GDBusMessage *message, | |
| gchar *blob, | |
| gsize blob_len) | |
| { | |
| MessageToWriteData *data; | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (blob != NULL); | |
| g_return_if_fail (blob_len > 16); | |
| data = g_slice_new0 (MessageToWriteData); | |
| data->worker = _g_dbus_worker_ref (worker); | |
| data->message = g_object_ref (message); | |
| data->blob = blob; /* steal! */ | |
| data->blob_size = blob_len; | |
| g_mutex_lock (&worker->write_lock); | |
| schedule_writing_unlocked (worker, data, NULL, NULL); | |
| g_mutex_unlock (&worker->write_lock); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| GDBusWorker * | |
| _g_dbus_worker_new (GIOStream *stream, | |
| GDBusCapabilityFlags capabilities, | |
| gboolean initially_frozen, | |
| GDBusWorkerMessageReceivedCallback message_received_callback, | |
| GDBusWorkerMessageAboutToBeSentCallback message_about_to_be_sent_callback, | |
| GDBusWorkerDisconnectedCallback disconnected_callback, | |
| gpointer user_data) | |
| { | |
| GDBusWorker *worker; | |
| GSource *idle_source; | |
| g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL); | |
| g_return_val_if_fail (message_received_callback != NULL, NULL); | |
| g_return_val_if_fail (message_about_to_be_sent_callback != NULL, NULL); | |
| g_return_val_if_fail (disconnected_callback != NULL, NULL); | |
| worker = g_new0 (GDBusWorker, 1); | |
| worker->ref_count = 1; | |
| g_mutex_init (&worker->read_lock); | |
| worker->message_received_callback = message_received_callback; | |
| worker->message_about_to_be_sent_callback = message_about_to_be_sent_callback; | |
| worker->disconnected_callback = disconnected_callback; | |
| worker->user_data = user_data; | |
| worker->stream = g_object_ref (stream); | |
| worker->capabilities = capabilities; | |
| worker->cancellable = g_cancellable_new (); | |
| worker->output_pending = PENDING_NONE; | |
| worker->frozen = initially_frozen; | |
| worker->received_messages_while_frozen = g_queue_new (); | |
| g_mutex_init (&worker->write_lock); | |
| worker->write_queue = g_queue_new (); | |
| if (G_IS_SOCKET_CONNECTION (worker->stream)) | |
| worker->socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)); | |
| worker->shared_thread_data = _g_dbus_shared_thread_ref (); | |
| /* begin reading */ | |
| idle_source = g_idle_source_new (); | |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); | |
| g_source_set_callback (idle_source, | |
| _g_dbus_worker_do_initial_read, | |
| _g_dbus_worker_ref (worker), | |
| (GDestroyNotify) _g_dbus_worker_unref); | |
| g_source_set_static_name (idle_source, "[gio] _g_dbus_worker_do_initial_read"); | |
| g_source_attach (idle_source, worker->shared_thread_data->context); | |
| g_source_unref (idle_source); | |
| return worker; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* can be called from any thread | |
| * | |
| * write_lock is not held on entry | |
| * output_pending may be anything | |
| */ | |
| void | |
| _g_dbus_worker_close (GDBusWorker *worker, | |
| GTask *task) | |
| { | |
| CloseData *close_data; | |
| close_data = g_slice_new0 (CloseData); | |
| close_data->worker = _g_dbus_worker_ref (worker); | |
| close_data->task = (task == NULL ? NULL : g_object_ref (task)); | |
| /* Don't set worker->close_expected here - we're in the wrong thread. | |
| * It'll be set before the actual close happens. | |
| */ | |
| g_cancellable_cancel (worker->cancellable); | |
| g_mutex_lock (&worker->write_lock); | |
| schedule_writing_unlocked (worker, NULL, NULL, close_data); | |
| g_mutex_unlock (&worker->write_lock); | |
| } | |
| /* This can be called from any thread - frees worker. Note that | |
| * callbacks might still happen if called from another thread than the | |
| * worker - use your own synchronization primitive in the callbacks. | |
| * | |
| * write_lock is not held on entry | |
| * output_pending may be anything | |
| */ | |
| void | |
| _g_dbus_worker_stop (GDBusWorker *worker) | |
| { | |
| g_atomic_int_set (&worker->stopped, TRUE); | |
| /* Cancel any pending operations and schedule a close of the underlying I/O | |
| * stream in the worker thread | |
| */ | |
| _g_dbus_worker_close (worker, NULL); | |
| /* _g_dbus_worker_close holds a ref until after an idle in the worker | |
| * thread has run, so we no longer need to unref in an idle like in | |
| * commit 322e25b535 | |
| */ | |
| _g_dbus_worker_unref (worker); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* can be called from any thread (except the worker thread) - blocks | |
| * calling thread until all queued outgoing messages are written and | |
| * the transport has been flushed | |
| * | |
| * write_lock is not held on entry | |
| * output_pending may be anything | |
| */ | |
| gboolean | |
| _g_dbus_worker_flush_sync (GDBusWorker *worker, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| gboolean ret; | |
| FlushData *data; | |
| guint64 pending_writes; | |
| data = NULL; | |
| ret = TRUE; | |
| g_mutex_lock (&worker->write_lock); | |
| /* if the queue is empty, no write is in-flight and we haven't written | |
| * anything since the last flush, then there's nothing to wait for | |
| */ | |
| pending_writes = g_queue_get_length (worker->write_queue); | |
| /* if a write is in-flight, we shouldn't be satisfied until the first | |
| * flush operation that follows it | |
| */ | |
| if (worker->output_pending == PENDING_WRITE) | |
| pending_writes += 1; | |
| if (pending_writes > 0 || | |
| worker->write_num_messages_written != worker->write_num_messages_flushed) | |
| { | |
| data = g_new0 (FlushData, 1); | |
| g_mutex_init (&data->mutex); | |
| g_cond_init (&data->cond); | |
| data->number_to_wait_for = worker->write_num_messages_written + pending_writes; | |
| data->finished = FALSE; | |
| g_mutex_lock (&data->mutex); | |
| schedule_writing_unlocked (worker, NULL, data, NULL); | |
| } | |
| g_mutex_unlock (&worker->write_lock); | |
| if (data != NULL) | |
| { | |
| /* Wait for flush operations to finish. */ | |
| while (!data->finished) | |
| { | |
| g_cond_wait (&data->cond, &data->mutex); | |
| } | |
| g_mutex_unlock (&data->mutex); | |
| g_cond_clear (&data->cond); | |
| g_mutex_clear (&data->mutex); | |
| if (data->error != NULL) | |
| { | |
| ret = FALSE; | |
| g_propagate_error (error, data->error); | |
| } | |
| g_free (data); | |
| } | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| #define G_DBUS_DEBUG_AUTHENTICATION (1<<0) | |
| #define G_DBUS_DEBUG_TRANSPORT (1<<1) | |
| #define G_DBUS_DEBUG_MESSAGE (1<<2) | |
| #define G_DBUS_DEBUG_PAYLOAD (1<<3) | |
| #define G_DBUS_DEBUG_CALL (1<<4) | |
| #define G_DBUS_DEBUG_SIGNAL (1<<5) | |
| #define G_DBUS_DEBUG_INCOMING (1<<6) | |
| #define G_DBUS_DEBUG_RETURN (1<<7) | |
| #define G_DBUS_DEBUG_EMISSION (1<<8) | |
| #define G_DBUS_DEBUG_ADDRESS (1<<9) | |
| #define G_DBUS_DEBUG_PROXY (1<<10) | |
| static gint _gdbus_debug_flags = 0; | |
| gboolean | |
| _g_dbus_debug_authentication (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_AUTHENTICATION) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_transport (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_TRANSPORT) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_message (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_MESSAGE) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_payload (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_PAYLOAD) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_call (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_CALL) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_signal (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_SIGNAL) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_incoming (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_INCOMING) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_return (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_RETURN) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_emission (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_EMISSION) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_address (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_ADDRESS) != 0; | |
| } | |
| gboolean | |
| _g_dbus_debug_proxy (void) | |
| { | |
| _g_dbus_initialize (); | |
| return (_gdbus_debug_flags & G_DBUS_DEBUG_PROXY) != 0; | |
| } | |
| G_LOCK_DEFINE_STATIC (print_lock); | |
| void | |
| _g_dbus_debug_print_lock (void) | |
| { | |
| G_LOCK (print_lock); | |
| } | |
| void | |
| _g_dbus_debug_print_unlock (void) | |
| { | |
| G_UNLOCK (print_lock); | |
| } | |
| /** | |
| * _g_dbus_initialize: | |
| * | |
| * Does various one-time init things such as | |
| * | |
| * - registering the G_DBUS_ERROR error domain | |
| * - parses the G_DBUS_DEBUG environment variable | |
| */ | |
| void | |
| _g_dbus_initialize (void) | |
| { | |
| static gsize initialized = 0; | |
| if (g_once_init_enter (&initialized)) | |
| { | |
| const gchar *debug; | |
| /* Ensure the domain is registered. */ | |
| g_dbus_error_quark (); | |
| debug = g_getenv ("G_DBUS_DEBUG"); | |
| if (debug != NULL) | |
| { | |
| const GDebugKey keys[] = { | |
| { "authentication", G_DBUS_DEBUG_AUTHENTICATION }, | |
| { "transport", G_DBUS_DEBUG_TRANSPORT }, | |
| { "message", G_DBUS_DEBUG_MESSAGE }, | |
| { "payload", G_DBUS_DEBUG_PAYLOAD }, | |
| { "call", G_DBUS_DEBUG_CALL }, | |
| { "signal", G_DBUS_DEBUG_SIGNAL }, | |
| { "incoming", G_DBUS_DEBUG_INCOMING }, | |
| { "return", G_DBUS_DEBUG_RETURN }, | |
| { "emission", G_DBUS_DEBUG_EMISSION }, | |
| { "address", G_DBUS_DEBUG_ADDRESS }, | |
| { "proxy", G_DBUS_DEBUG_PROXY } | |
| }; | |
| _gdbus_debug_flags = g_parse_debug_string (debug, keys, G_N_ELEMENTS (keys)); | |
| if (_gdbus_debug_flags & G_DBUS_DEBUG_PAYLOAD) | |
| _gdbus_debug_flags |= G_DBUS_DEBUG_MESSAGE; | |
| } | |
| /* Work-around for https://bugzilla.gnome.org/show_bug.cgi?id=627724 */ | |
| ensure_required_types (); | |
| g_once_init_leave (&initialized, 1); | |
| } | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| GVariantType * | |
| _g_dbus_compute_complete_signature (GDBusArgInfo **args) | |
| { | |
| const GVariantType *arg_types[256]; | |
| guint n; | |
| if (args) | |
| for (n = 0; args[n] != NULL; n++) | |
| { | |
| /* DBus places a hard limit of 255 on signature length. | |
| * therefore number of args must be less than 256. | |
| */ | |
| g_assert (n < 256); | |
| arg_types[n] = G_VARIANT_TYPE (args[n]->signature); | |
| if G_UNLIKELY (arg_types[n] == NULL) | |
| return NULL; | |
| } | |
| else | |
| n = 0; | |
| return g_variant_type_new_tuple (arg_types, n); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| #ifdef G_OS_WIN32 | |
| #define DBUS_DAEMON_ADDRESS_INFO L"DBusDaemonAddressInfo" | |
| #define DBUS_DAEMON_MUTEX L"DBusDaemonMutex" | |
| #define UNIQUE_DBUS_INIT_MUTEX L"UniqueDBusInitMutex" | |
| #define DBUS_AUTOLAUNCH_MUTEX L"DBusAutolaunchMutex" | |
| static void | |
| release_mutex (HANDLE mutex) | |
| { | |
| ReleaseMutex (mutex); | |
| CloseHandle (mutex); | |
| } | |
| static HANDLE | |
| acquire_mutex (const wchar_t *mutexname) | |
| { | |
| HANDLE mutex; | |
| DWORD res; | |
| mutex = CreateMutex (NULL, FALSE, mutexname); | |
| if (!mutex) | |
| return 0; | |
| res = WaitForSingleObject (mutex, INFINITE); | |
| switch (res) | |
| { | |
| case WAIT_ABANDONED: | |
| release_mutex (mutex); | |
| return 0; | |
| case WAIT_FAILED: | |
| case WAIT_TIMEOUT: | |
| return 0; | |
| } | |
| return mutex; | |
| } | |
| static gboolean | |
| is_mutex_owned (const wchar_t *mutexname) | |
| { | |
| HANDLE mutex; | |
| gboolean res = FALSE; | |
| mutex = CreateMutex (NULL, FALSE, mutexname); | |
| if (WaitForSingleObject (mutex, 10) == WAIT_TIMEOUT) | |
| res = TRUE; | |
| else | |
| ReleaseMutex (mutex); | |
| CloseHandle (mutex); | |
| return res; | |
| } | |
| static char * | |
| read_shm (const wchar_t *shm_name) | |
| { | |
| HANDLE shared_mem; | |
| char *shared_data; | |
| char *res; | |
| int i; | |
| res = NULL; | |
| for (i = 0; i < 20; i++) | |
| { | |
| shared_mem = OpenFileMapping (FILE_MAP_READ, FALSE, shm_name); | |
| if (shared_mem != 0) | |
| break; | |
| Sleep (100); | |
| } | |
| if (shared_mem != 0) | |
| { | |
| shared_data = MapViewOfFile (shared_mem, FILE_MAP_READ, 0, 0, 0); | |
| /* It looks that a race is possible here: | |
| * if the dbus process already created mapping but didn't fill it | |
| * the code below may read incorrect address. | |
| * Also this is a bit complicated by the fact that | |
| * any change in the "synchronization contract" between processes | |
| * should be accompanied with renaming all of used win32 named objects: | |
| * otherwise libgio-2.0-0.dll of different versions shipped with | |
| * different apps may break each other due to protocol difference. | |
| */ | |
| if (shared_data != NULL) | |
| { | |
| res = g_strdup (shared_data); | |
| UnmapViewOfFile (shared_data); | |
| } | |
| CloseHandle (shared_mem); | |
| } | |
| return res; | |
| } | |
| static HANDLE | |
| set_shm (const wchar_t *shm_name, const char *value) | |
| { | |
| HANDLE shared_mem; | |
| char *shared_data; | |
| shared_mem = CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, | |
| 0, strlen (value) + 1, shm_name); | |
| if (shared_mem == 0) | |
| return 0; | |
| shared_data = MapViewOfFile (shared_mem, FILE_MAP_WRITE, 0, 0, 0 ); | |
| if (shared_data == NULL) | |
| return 0; | |
| strcpy (shared_data, value); | |
| UnmapViewOfFile (shared_data); | |
| return shared_mem; | |
| } | |
| /* These keep state between publish_session_bus and unpublish_session_bus */ | |
| static HANDLE published_daemon_mutex; | |
| static HANDLE published_shared_mem; | |
| static gboolean | |
| publish_session_bus (const char *address) | |
| { | |
| HANDLE init_mutex; | |
| init_mutex = acquire_mutex (UNIQUE_DBUS_INIT_MUTEX); | |
| published_daemon_mutex = CreateMutex (NULL, FALSE, DBUS_DAEMON_MUTEX); | |
| if (WaitForSingleObject (published_daemon_mutex, 10 ) != WAIT_OBJECT_0) | |
| { | |
| release_mutex (init_mutex); | |
| CloseHandle (published_daemon_mutex); | |
| published_daemon_mutex = NULL; | |
| return FALSE; | |
| } | |
| published_shared_mem = set_shm (DBUS_DAEMON_ADDRESS_INFO, address); | |
| if (!published_shared_mem) | |
| { | |
| release_mutex (init_mutex); | |
| CloseHandle (published_daemon_mutex); | |
| published_daemon_mutex = NULL; | |
| return FALSE; | |
| } | |
| release_mutex (init_mutex); | |
| return TRUE; | |
| } | |
| static void | |
| unpublish_session_bus (void) | |
| { | |
| HANDLE init_mutex; | |
| init_mutex = acquire_mutex (UNIQUE_DBUS_INIT_MUTEX); | |
| CloseHandle (published_shared_mem); | |
| published_shared_mem = NULL; | |
| release_mutex (published_daemon_mutex); | |
| published_daemon_mutex = NULL; | |
| release_mutex (init_mutex); | |
| } | |
| static void | |
| wait_console_window (void) | |
| { | |
| FILE *console = fopen ("CONOUT$", "w"); | |
| SetConsoleTitleW (L"gdbus-daemon output. Type any character to close this window."); | |
| fprintf (console, _("(Type any character to close this window)\n")); | |
| fflush (console); | |
| _getch (); | |
| } | |
| static void | |
| open_console_window (void) | |
| { | |
| if (((HANDLE) _get_osfhandle (fileno (stdout)) == INVALID_HANDLE_VALUE || | |
| (HANDLE) _get_osfhandle (fileno (stderr)) == INVALID_HANDLE_VALUE) && AllocConsole ()) | |
| { | |
| if ((HANDLE) _get_osfhandle (fileno (stdout)) == INVALID_HANDLE_VALUE) | |
| freopen ("CONOUT$", "w", stdout); | |
| if ((HANDLE) _get_osfhandle (fileno (stderr)) == INVALID_HANDLE_VALUE) | |
| freopen ("CONOUT$", "w", stderr); | |
| SetConsoleTitleW (L"gdbus-daemon debug output."); | |
| atexit (wait_console_window); | |
| } | |
| } | |
| static void | |
| idle_timeout_cb (GDBusDaemon *daemon, gpointer user_data) | |
| { | |
| GMainLoop *loop = user_data; | |
| g_main_loop_quit (loop); | |
| } | |
| /* Satisfies STARTF_FORCEONFEEDBACK */ | |
| static void | |
| turn_off_the_starting_cursor (void) | |
| { | |
| MSG msg; | |
| BOOL bRet; | |
| PostQuitMessage (0); | |
| while ((bRet = GetMessage (&msg, 0, 0, 0)) != 0) | |
| { | |
| if (bRet == -1) | |
| continue; | |
| TranslateMessage (&msg); | |
| DispatchMessage (&msg); | |
| } | |
| } | |
| void __stdcall | |
| g_win32_run_session_bus (void* hwnd, void* hinst, const char* cmdline, int cmdshow) | |
| { | |
| GDBusDaemon *daemon; | |
| GMainLoop *loop; | |
| const char *address; | |
| GError *error = NULL; | |
| turn_off_the_starting_cursor (); | |
| if (g_getenv ("GDBUS_DAEMON_DEBUG") != NULL) | |
| open_console_window (); | |
| address = "nonce-tcp:"; | |
| daemon = _g_dbus_daemon_new (address, NULL, &error); | |
| if (daemon == NULL) | |
| { | |
| g_printerr ("Can't init bus: %s\n", error->message); | |
| g_error_free (error); | |
| return; | |
| } | |
| loop = g_main_loop_new (NULL, FALSE); | |
| /* There is a subtle detail with "idle-timeout" signal of dbus daemon: | |
| * It is fired on idle after last client disconnection, | |
| * but (at least with glib 2.59.1) it is NEVER fired | |
| * if no clients connect to daemon at all. | |
| * This may lead to infinite run of this daemon process. | |
| */ | |
| g_signal_connect (daemon, "idle-timeout", G_CALLBACK (idle_timeout_cb), loop); | |
| if (publish_session_bus (_g_dbus_daemon_get_address (daemon))) | |
| { | |
| g_main_loop_run (loop); | |
| unpublish_session_bus (); | |
| } | |
| g_main_loop_unref (loop); | |
| g_object_unref (daemon); | |
| } | |
| static gboolean autolaunch_binary_absent = FALSE; | |
| static wchar_t * | |
| find_dbus_process_path (void) | |
| { | |
| wchar_t *dbus_path; | |
| gchar *exe_path = GLIB_PRIVATE_CALL (g_win32_find_helper_executable_path) ("gdbus.exe", _g_io_win32_get_module ()); | |
| dbus_path = g_utf8_to_utf16 (exe_path, -1, NULL, NULL, NULL); | |
| g_free (exe_path); | |
| if (dbus_path == NULL) | |
| return NULL; | |
| if (GetFileAttributesW (dbus_path) == INVALID_FILE_ATTRIBUTES) | |
| { | |
| g_free (dbus_path); | |
| return NULL; | |
| } | |
| return dbus_path; | |
| } | |
| gchar * | |
| _g_dbus_win32_get_session_address_dbus_launch (GError **error) | |
| { | |
| HANDLE autolaunch_mutex, init_mutex; | |
| char *address = NULL; | |
| autolaunch_mutex = acquire_mutex (DBUS_AUTOLAUNCH_MUTEX); | |
| init_mutex = acquire_mutex (UNIQUE_DBUS_INIT_MUTEX); | |
| if (is_mutex_owned (DBUS_DAEMON_MUTEX)) | |
| address = read_shm (DBUS_DAEMON_ADDRESS_INFO); | |
| release_mutex (init_mutex); | |
| if (address == NULL && !autolaunch_binary_absent) | |
| { | |
| wchar_t *dbus_path = find_dbus_process_path (); | |
| if (dbus_path == NULL) | |
| { | |
| /* warning won't be raised another time | |
| * since autolaunch_binary_absent would be already set. | |
| */ | |
| autolaunch_binary_absent = TRUE; | |
| g_warning ("win32 session dbus binary not found"); | |
| } | |
| else | |
| { | |
| PROCESS_INFORMATION pi = { 0 }; | |
| STARTUPINFOW si = { 0 }; | |
| BOOL res = FALSE; | |
| wchar_t args[MAX_PATH * 2 + 100] = { 0 }; | |
| wchar_t working_dir[MAX_PATH + 2] = { 0 }; | |
| wchar_t *p; | |
| wcscpy (working_dir, dbus_path); | |
| p = wcsrchr (working_dir, L'\\'); | |
| if (p != NULL) | |
| *p = L'\0'; | |
| wcscpy (args, L"\""); | |
| wcscat (args, dbus_path); | |
| wcscat (args, L"\" "); | |
| #define _L_PREFIX_FOR_EXPANDED(arg) L##arg | |
| #define _L_PREFIX(arg) _L_PREFIX_FOR_EXPANDED (arg) | |
| wcscat (args, _L_PREFIX (_GDBUS_ARG_WIN32_RUN_SESSION_BUS)); | |
| #undef _L_PREFIX | |
| #undef _L_PREFIX_FOR_EXPANDED | |
| res = CreateProcessW (dbus_path, args, | |
| 0, 0, FALSE, | |
| NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | DETACHED_PROCESS, | |
| 0, working_dir, | |
| &si, &pi); | |
| if (res) | |
| { | |
| address = read_shm (DBUS_DAEMON_ADDRESS_INFO); | |
| if (address == NULL) | |
| g_warning ("%S dbus binary failed to launch bus, maybe incompatible version", dbus_path); | |
| } | |
| g_free (dbus_path); | |
| } | |
| } | |
| release_mutex (autolaunch_mutex); | |
| if (address == NULL) | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_FAILED, | |
| _("Session dbus not running, and autolaunch failed")); | |
| return address; | |
| } | |
| #endif | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| gchar * | |
| _g_dbus_get_machine_id (GError **error) | |
| { | |
| #ifdef G_OS_WIN32 | |
| HW_PROFILE_INFO info; | |
| char *guid, *src, *dest, *res; | |
| int i; | |
| if (!GetCurrentHwProfile (&info)) | |
| { | |
| char *message = g_win32_error_message (GetLastError ()); | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_FAILED, | |
| _("Unable to get Hardware profile: %s"), message); | |
| g_free (message); | |
| return NULL; | |
| } | |
| if (!(guid = g_utf16_to_utf8 (info.szHwProfileGuid, -1, NULL, NULL, NULL))) | |
| return NULL; | |
| /* Guid is of the form: {12340001-4980-1920-6788-123456789012} */ | |
| src = guid; | |
| res = g_malloc (32+1); | |
| dest = res; | |
| src++; /* Skip { */ | |
| for (i = 0; i < 8; i++) | |
| *dest++ = *src++; | |
| src++; /* Skip - */ | |
| for (i = 0; i < 4; i++) | |
| *dest++ = *src++; | |
| src++; /* Skip - */ | |
| for (i = 0; i < 4; i++) | |
| *dest++ = *src++; | |
| src++; /* Skip - */ | |
| for (i = 0; i < 4; i++) | |
| *dest++ = *src++; | |
| src++; /* Skip - */ | |
| for (i = 0; i < 12; i++) | |
| *dest++ = *src++; | |
| *dest = 0; | |
| g_free (guid); | |
| return res; | |
| #else | |
| gchar *ret = NULL; | |
| GError *first_error = NULL; | |
| gsize i; | |
| gboolean non_zero = FALSE; | |
| /* Copy what dbus.git does: allow the /var/lib path to be configurable at | |
| * build time, but hard-code the system-wide machine ID path in /etc. */ | |
| const gchar *var_lib_path = LOCALSTATEDIR "/lib/dbus/machine-id"; | |
| const gchar *etc_path = "/etc/machine-id"; | |
| if (!g_file_get_contents (var_lib_path, | |
| &ret, | |
| NULL, | |
| &first_error) && | |
| !g_file_get_contents (etc_path, | |
| &ret, | |
| NULL, | |
| NULL)) | |
| { | |
| g_propagate_prefixed_error (error, g_steal_pointer (&first_error), | |
| /* Translators: Both placeholders are file paths */ | |
| _("Unable to load %s or %s: "), | |
| var_lib_path, etc_path); | |
| return NULL; | |
| } | |
| /* ignore the error from the first try, if any */ | |
| g_clear_error (&first_error); | |
| /* Validate the machine ID. From `man 5 machine-id`: | |
| * > The machine ID is a single newline-terminated, hexadecimal, 32-character, | |
| * > lowercase ID. When decoded from hexadecimal, this corresponds to a | |
| * > 16-byte/128-bit value. This ID may not be all zeros. | |
| */ | |
| for (i = 0; ret[i] != '\0' && ret[i] != '\n'; i++) | |
| { | |
| /* Break early if it’s invalid. */ | |
| if (!g_ascii_isxdigit (ret[i]) || g_ascii_isupper (ret[i])) | |
| break; | |
| if (ret[i] != '0') | |
| non_zero = TRUE; | |
| } | |
| if (i != 32 || ret[i] != '\n' || ret[i + 1] != '\0' || !non_zero) | |
| { | |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, | |
| "Invalid machine ID in %s or %s", | |
| var_lib_path, etc_path); | |
| g_free (ret); | |
| return NULL; | |
| } | |
| /* Strip trailing newline. */ | |
| ret[32] = '\0'; | |
| return g_steal_pointer (&ret); | |
| #endif | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| gchar * | |
| _g_dbus_enum_to_string (GType enum_type, gint value) | |
| { | |
| gchar *ret; | |
| GEnumClass *klass; | |
| GEnumValue *enum_value; | |
| klass = g_type_class_ref (enum_type); | |
| enum_value = g_enum_get_value (klass, value); | |
| if (enum_value != NULL) | |
| ret = g_strdup (enum_value->value_nick); | |
| else | |
| ret = g_strdup_printf ("unknown (value %d)", value); | |
| g_type_class_unref (klass); | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static void | |
| write_message_print_transport_debug (gssize bytes_written, | |
| MessageToWriteData *data) | |
| { | |
| if (G_LIKELY (!_g_dbus_debug_transport ())) | |
| goto out; | |
| _g_dbus_debug_print_lock (); | |
| g_print ("========================================================================\n" | |
| "GDBus-debug:Transport:\n" | |
| " >>>> WROTE %" G_GSSIZE_FORMAT " bytes of message with serial %d and\n" | |
| " size %" G_GSIZE_FORMAT " from offset %" G_GSIZE_FORMAT " on a %s\n", | |
| bytes_written, | |
| g_dbus_message_get_serial (data->message), | |
| data->blob_size, | |
| data->total_written, | |
| g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_output_stream (data->worker->stream)))); | |
| _g_dbus_debug_print_unlock (); | |
| out: | |
| ; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static void | |
| read_message_print_transport_debug (gssize bytes_read, | |
| GDBusWorker *worker) | |
| { | |
| gsize size; | |
| gint32 serial; | |
| gint32 message_length; | |
| if (G_LIKELY (!_g_dbus_debug_transport ())) | |
| goto out; | |
| size = bytes_read + worker->read_buffer_cur_size; | |
| serial = 0; | |
| message_length = 0; | |
| if (size >= 16) | |
| message_length = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer, size, NULL); | |
| if (size >= 1) | |
| { | |
| switch (worker->read_buffer[0]) | |
| { | |
| case 'l': | |
| if (size >= 12) | |
| serial = GUINT32_FROM_LE (((guint32 *) worker->read_buffer)[2]); | |
| break; | |
| case 'B': | |
| if (size >= 12) | |
| serial = GUINT32_FROM_BE (((guint32 *) worker->read_buffer)[2]); | |
| break; | |
| default: | |
| /* an error will be set elsewhere if this happens */ | |
| goto out; | |
| } | |
| } | |
| _g_dbus_debug_print_lock (); | |
| g_print ("========================================================================\n" | |
| "GDBus-debug:Transport:\n" | |
| " <<<< READ %" G_GSSIZE_FORMAT " bytes of message with serial %d and\n" | |
| " size %d to offset %" G_GSIZE_FORMAT " from a %s\n", | |
| bytes_read, | |
| serial, | |
| message_length, | |
| worker->read_buffer_cur_size, | |
| g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_input_stream (worker->stream)))); | |
| _g_dbus_debug_print_unlock (); | |
| out: | |
| ; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| gboolean | |
| _g_signal_accumulator_false_handled (GSignalInvocationHint *ihint, | |
| GValue *return_accu, | |
| const GValue *handler_return, | |
| gpointer dummy) | |
| { | |
| gboolean continue_emission; | |
| gboolean signal_return; | |
| signal_return = g_value_get_boolean (handler_return); | |
| g_value_set_boolean (return_accu, signal_return); | |
| continue_emission = signal_return; | |
| return continue_emission; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static void | |
| append_nibble (GString *s, gint val) | |
| { | |
| g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val)); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| gchar * | |
| _g_dbus_hexencode (const gchar *str, | |
| gsize str_len) | |
| { | |
| gsize n; | |
| GString *s; | |
| s = g_string_new (NULL); | |
| for (n = 0; n < str_len; n++) | |
| { | |
| gint val; | |
| gint upper_nibble; | |
| gint lower_nibble; | |
| val = ((const guchar *) str)[n]; | |
| upper_nibble = val >> 4; | |
| lower_nibble = val & 0x0f; | |
| append_nibble (s, upper_nibble); | |
| append_nibble (s, lower_nibble); | |
| } | |
| return g_string_free (s, FALSE); | |
| } |