diff options
Diffstat (limited to 'src/gs-restarter.c')
-rw-r--r-- | src/gs-restarter.c | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/gs-restarter.c b/src/gs-restarter.c new file mode 100644 index 0000000..f4c0b47 --- /dev/null +++ b/src/gs-restarter.c @@ -0,0 +1,216 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2017 Richard Hughes <richard@hughsie.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <gio/gio.h> +#include <stdlib.h> + +#define GS_BINARY_NAME "gnome-software" +#define GS_DBUS_BUS_NAME "org.gnome.Software" +#define GS_DBUS_OBJECT_PATH "/org/gnome/Software" +#define GS_DBUS_INTERFACE_NAME "org.gtk.Actions" + +typedef struct { + GMainLoop *loop; + GDBusConnection *connection; + gboolean is_running; + gboolean timed_out; +} GsRestarterPrivate; + +static void +gs_restarter_on_name_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data; + priv->is_running = TRUE; + g_debug ("%s appeared", GS_DBUS_BUS_NAME); + if (g_main_loop_is_running (priv->loop)) + g_main_loop_quit (priv->loop); +} + +static void +gs_restarter_on_name_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data; + priv->is_running = FALSE; + g_debug ("%s vanished", GS_DBUS_BUS_NAME); + if (g_main_loop_is_running (priv->loop)) + g_main_loop_quit (priv->loop); +} + +static gboolean +gs_restarter_loop_timeout_cb (gpointer user_data) +{ + GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data; + priv->timed_out = TRUE; + g_main_loop_quit (priv->loop); + return TRUE; +} + +static gboolean +gs_restarter_wait_for_timeout (GsRestarterPrivate *priv, + guint timeout_ms, + GError **error) +{ + guint timer_id; + priv->timed_out = FALSE; + timer_id = g_timeout_add (timeout_ms, gs_restarter_loop_timeout_cb, priv); + g_main_loop_run (priv->loop); + g_source_remove (timer_id); + if (priv->timed_out) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "Waited for %ums", timeout_ms); + return FALSE; + } + return TRUE; +} + +static GsRestarterPrivate * +gs_restarter_private_new (void) +{ + GsRestarterPrivate *priv = g_new0 (GsRestarterPrivate, 1); + priv->loop = g_main_loop_new (NULL, FALSE); + return priv; +} + +static void +gs_restarter_private_free (GsRestarterPrivate *priv) +{ + if (priv->connection != NULL) + g_object_unref (priv->connection); + g_main_loop_unref (priv->loop); + g_free (priv); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsRestarterPrivate, gs_restarter_private_free) + +static gboolean +gs_restarter_create_new_process (GsRestarterPrivate *priv, GError **error) +{ + g_autofree gchar *binary_filename = NULL; + + /* start executable */ + binary_filename = g_build_filename (BINDIR, GS_BINARY_NAME, NULL); + g_debug ("starting new binary %s", binary_filename); + if (!g_spawn_command_line_async (binary_filename, error)) + return FALSE; + + /* wait for the bus name to appear */ + if (!gs_restarter_wait_for_timeout (priv, 15000, error)) { + g_prefix_error (error, "%s did not appear: ", GS_DBUS_BUS_NAME); + return FALSE; + } + + return TRUE; +} + +static gboolean +gs_restarter_destroy_old_process (GsRestarterPrivate *priv, GError **error) +{ + GVariantBuilder args_params; + GVariantBuilder args_platform_data; + g_autoptr(GVariant) reply = NULL; + + /* call a GtkAction */ + g_variant_builder_init (&args_params, g_variant_type_new ("av")); + g_variant_builder_init (&args_platform_data, g_variant_type_new ("a{sv}")); + reply = g_dbus_connection_call_sync (priv->connection, + GS_DBUS_BUS_NAME, + GS_DBUS_OBJECT_PATH, + GS_DBUS_INTERFACE_NAME, + "Activate", + g_variant_new ("(sava{sv})", + "shutdown", + &args_params, + &args_platform_data), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + 5000, + NULL, + error); + if (reply == NULL) { + g_prefix_error (error, "Failed to send RequestShutdown: "); + return FALSE; + } + + /* wait for the name to disappear from the bus */ + if (!gs_restarter_wait_for_timeout (priv, 30000, error)) { + g_prefix_error (error, "Failed to see %s vanish: ", GS_DBUS_BUS_NAME); + return FALSE; + } + + return TRUE; +} + +static gboolean +gs_restarter_setup_watcher (GsRestarterPrivate *priv, GError **error) +{ + /* watch the name appear and vanish */ + priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + if (priv->connection == NULL) { + g_prefix_error (error, "Failed to get D-Bus connection: "); + return FALSE; + } + g_bus_watch_name_on_connection (priv->connection, + GS_DBUS_BUS_NAME, + G_BUS_NAME_WATCHER_FLAGS_NONE, + gs_restarter_on_name_appeared_cb, + gs_restarter_on_name_vanished_cb, + priv, + NULL); + + /* wait for one of the callbacks to be called */ + if (!gs_restarter_wait_for_timeout (priv, 50, error)) { + g_prefix_error (error, "Failed to watch %s: ", GS_DBUS_BUS_NAME); + return FALSE; + } + + return TRUE; +} + +int +main (int argc, char **argv) +{ + g_autoptr(GsRestarterPrivate) priv = NULL; + g_autoptr(GError) error = NULL; + + /* show all debugging */ + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + + /* set up the watcher */ + priv = gs_restarter_private_new (); + if (!gs_restarter_setup_watcher (priv, &error)) { + g_warning ("Failed to set up: %s", error->message); + return EXIT_FAILURE; + } + + /* kill the old process */ + if (priv->is_running) { + if (!gs_restarter_destroy_old_process (priv, &error)) { + g_warning ("Failed to quit service: %s", error->message); + return EXIT_FAILURE; + } + } + + /* start a new process */ + if (!gs_restarter_create_new_process (priv, &error)) { + g_warning ("Failed to start service: %s", error->message); + return EXIT_FAILURE; + } + + /* success */ + g_debug ("%s process successfully restarted", GS_DBUS_BUS_NAME); + return EXIT_SUCCESS; +} |