/* ide-worker-process.c * * Copyright 2015-2019 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-worker-process" #include "config.h" #include #include #include #include "ide-worker-process.h" #include "ide-worker.h" struct _IdeWorkerProcess { GObject parent_instance; gchar *argv0; gchar *dbus_address; gchar *plugin_name; GSubprocess *subprocess; GDBusConnection *connection; GPtrArray *tasks; IdeWorker *worker; guint quit : 1; }; G_DEFINE_FINAL_TYPE (IdeWorkerProcess, ide_worker_process, G_TYPE_OBJECT) DZL_DEFINE_COUNTER (instances, "IdeWorkerProcess", "Instances", "Number of IdeWorkerProcess instances") enum { PROP_0, PROP_ARGV0, PROP_PLUGIN_NAME, PROP_DBUS_ADDRESS, LAST_PROP }; static GParamSpec *gParamSpecs [LAST_PROP]; static void ide_worker_process_respawn (IdeWorkerProcess *self); IdeWorkerProcess * ide_worker_process_new (const gchar *argv0, const gchar *plugin_name, const gchar *dbus_address) { IdeWorkerProcess *ret; IDE_ENTRY; g_return_val_if_fail (argv0 != NULL, NULL); g_return_val_if_fail (plugin_name != NULL, NULL); g_return_val_if_fail (dbus_address != NULL, NULL); ret = g_object_new (IDE_TYPE_WORKER_PROCESS, "argv0", argv0, "plugin-name", plugin_name, "dbus-address", dbus_address, NULL); IDE_RETURN (ret); } static void ide_worker_process_wait_check_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = (GSubprocess *)object; g_autoptr(IdeWorkerProcess) self = user_data; g_autoptr(GError) error = NULL; IDE_ENTRY; g_assert (G_IS_SUBPROCESS (subprocess)); g_assert (IDE_IS_WORKER_PROCESS (self)); g_assert (G_IS_ASYNC_RESULT (result)); if (!g_subprocess_wait_check_finish (subprocess, result, &error)) { if (!self->quit) g_warning ("%s", error->message); } g_clear_object (&self->subprocess); if (!self->quit) ide_worker_process_respawn (self); IDE_EXIT; } static void ide_worker_process_respawn (IdeWorkerProcess *self) { g_autoptr(GSubprocessLauncher) launcher = NULL; g_autoptr(GSubprocess) subprocess = NULL; g_autofree gchar *plugin = NULL; g_autofree gchar *dbus_address = NULL; g_autoptr(GString) verbosearg = NULL; GError *error = NULL; GPtrArray *args; gint verbosity; gint i; IDE_ENTRY; g_assert (IDE_IS_WORKER_PROCESS (self)); g_assert (self->subprocess == NULL); plugin = g_strdup_printf ("--plugin=%s", self->plugin_name); dbus_address = g_strdup_printf ("--dbus-address=%s", self->dbus_address); verbosearg = g_string_new ("-"); verbosity = ide_log_get_verbosity (); for (i = 0; i < verbosity; i++) g_string_append_c (verbosearg, 'v'); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); args = g_ptr_array_new (); g_ptr_array_add (args, self->argv0); /* gnome-builder */ g_ptr_array_add (args,(gchar *) "--type=worker"); g_ptr_array_add (args, plugin); /* --plugin= */ g_ptr_array_add (args, dbus_address); /* --dbus-address= */ g_ptr_array_add (args, verbosity > 0 ? verbosearg->str : NULL); g_ptr_array_add (args, NULL); #ifdef IDE_ENABLE_TRACE { g_autofree gchar *str = NULL; str = g_strjoinv (" ", (gchar **)args->pdata); IDE_TRACE_MSG ("Launching '%s'", str); } #endif subprocess = g_subprocess_launcher_spawnv (launcher, (const gchar * const *)args->pdata, &error); g_ptr_array_free (args, TRUE); if (subprocess == NULL) { g_warning ("Failed to spawn %s", error->message); g_clear_error (&error); IDE_EXIT; } self->subprocess = g_object_ref (subprocess); g_subprocess_wait_check_async (subprocess, NULL, ide_worker_process_wait_check_cb, g_object_ref (self)); if (self->worker == NULL) { PeasEngine *engine; PeasExtension *exten; PeasPluginInfo *plugin_info; engine = peas_engine_get_default (); plugin_info = peas_engine_get_plugin_info (engine, self->plugin_name); if (plugin_info != NULL) { exten = peas_engine_create_extension (engine, plugin_info, IDE_TYPE_WORKER, NULL); self->worker = IDE_WORKER (exten); } } IDE_EXIT; } void ide_worker_process_run (IdeWorkerProcess *self) { g_return_if_fail (IDE_IS_WORKER_PROCESS (self)); g_return_if_fail (self->subprocess == NULL); ide_worker_process_respawn (self); } void ide_worker_process_quit (IdeWorkerProcess *self) { g_return_if_fail (IDE_IS_WORKER_PROCESS (self)); self->quit = TRUE; if (self->subprocess != NULL) { g_autoptr(GSubprocess) subprocess = g_steal_pointer (&self->subprocess); g_subprocess_force_exit (subprocess); } } static void ide_worker_process_dispose (GObject *object) { IdeWorkerProcess *self = (IdeWorkerProcess *)object; if (self->subprocess != NULL) ide_worker_process_quit (self); G_OBJECT_CLASS (ide_worker_process_parent_class)->dispose (object); } static void ide_worker_process_finalize (GObject *object) { IdeWorkerProcess *self = (IdeWorkerProcess *)object; g_clear_pointer (&self->argv0, g_free); g_clear_pointer (&self->plugin_name, g_free); g_clear_pointer (&self->dbus_address, g_free); g_clear_pointer (&self->tasks, g_ptr_array_unref); g_clear_object (&self->connection); g_clear_object (&self->subprocess); g_clear_object (&self->worker); G_OBJECT_CLASS (ide_worker_process_parent_class)->finalize (object); DZL_COUNTER_DEC (instances); } static void ide_worker_process_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { IdeWorkerProcess *self = IDE_WORKER_PROCESS (object); switch (prop_id) { case PROP_ARGV0: g_value_set_string (value, self->argv0); break; case PROP_PLUGIN_NAME: g_value_set_string (value, self->plugin_name); break; case PROP_DBUS_ADDRESS: g_value_set_string (value, self->dbus_address); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_worker_process_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { IdeWorkerProcess *self = IDE_WORKER_PROCESS (object); switch (prop_id) { case PROP_ARGV0: self->argv0 = g_value_dup_string (value); break; case PROP_PLUGIN_NAME: self->plugin_name = g_value_dup_string (value); break; case PROP_DBUS_ADDRESS: self->dbus_address = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_worker_process_class_init (IdeWorkerProcessClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = ide_worker_process_dispose; object_class->finalize = ide_worker_process_finalize; object_class->get_property = ide_worker_process_get_property; object_class->set_property = ide_worker_process_set_property; gParamSpecs [PROP_ARGV0] = g_param_spec_string ("argv0", "Argv0", "Argv0", NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); gParamSpecs [PROP_PLUGIN_NAME] = g_param_spec_string ("plugin-name", "plugin-name", "plugin-name", NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); gParamSpecs [PROP_DBUS_ADDRESS] = g_param_spec_string ("dbus-address", "dbus-address", "dbus-address", NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs); } static void ide_worker_process_init (IdeWorkerProcess *self) { DZL_COUNTER_INC (instances); } gboolean ide_worker_process_matches_credentials (IdeWorkerProcess *self, GCredentials *credentials) { g_autofree gchar *str = NULL; const gchar *identifier; pid_t pid; g_return_val_if_fail (IDE_IS_WORKER_PROCESS (self), FALSE); g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE); if ((self->subprocess != NULL) && (identifier = g_subprocess_get_identifier (self->subprocess)) && (pid = g_credentials_get_unix_pid (credentials, NULL)) != -1) { str = g_strdup_printf ("%d", (int)pid); if (g_strcmp0 (identifier, str) == 0) return TRUE; } return FALSE; } static void ide_worker_process_create_proxy_for_task (IdeWorkerProcess *self, IdeTask *task) { GDBusProxy *proxy; g_autoptr(GError) error = NULL; IDE_ENTRY; g_assert (IDE_IS_WORKER_PROCESS (self)); g_assert (IDE_IS_TASK (task)); if (self->worker == NULL) { ide_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, "Failed to create IdeWorker instance."); IDE_EXIT; } proxy = ide_worker_create_proxy (self->worker, self->connection, &error); if (proxy == NULL) { if (error == NULL) error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, "IdeWorker returned NULL and did not set an error."); ide_task_return_error (task, g_steal_pointer (&error)); IDE_EXIT; } ide_task_return_pointer (task, proxy, g_object_unref); IDE_EXIT; } void ide_worker_process_set_connection (IdeWorkerProcess *self, GDBusConnection *connection) { g_return_if_fail (IDE_IS_WORKER_PROCESS (self)); g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); if (g_set_object (&self->connection, connection)) { if (self->tasks != NULL) { g_autoptr(GPtrArray) ar = NULL; guint i; ar = self->tasks; self->tasks = NULL; for (i = 0; i < ar->len; i++) { IdeTask *task = g_ptr_array_index (ar, i); ide_worker_process_create_proxy_for_task (self, task); } } } } void ide_worker_process_get_proxy_async (IdeWorkerProcess *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(IdeTask) task = NULL; IDE_ENTRY; g_return_if_fail (IDE_IS_WORKER_PROCESS (self)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); task = ide_task_new (self, cancellable, callback, user_data); if (self->connection != NULL) { ide_worker_process_create_proxy_for_task (self, task); IDE_EXIT; } if (self->tasks == NULL) self->tasks = g_ptr_array_new_with_free_func (g_object_unref); g_ptr_array_add (self->tasks, g_object_ref (task)); IDE_EXIT; } GDBusProxy * ide_worker_process_get_proxy_finish (IdeWorkerProcess *self, GAsyncResult *result, GError **error) { IdeTask *task = (IdeTask *)result; GDBusProxy *ret; IDE_ENTRY; g_return_val_if_fail (IDE_IS_WORKER_PROCESS (self), NULL); g_return_val_if_fail (IDE_IS_TASK (task), NULL); ret = ide_task_propagate_pointer (task, error); IDE_RETURN (ret); }