[gnome-builder/wip/gtk4-port: 1576/1774] plugins/deviced: implement basic command proxy




commit 18fa1809bc1a661e2a64693fbd52714e8293a660
Author: Christian Hergert <chergert redhat com>
Date:   Sun Jun 19 22:22:16 2022 -0700

    plugins/deviced: implement basic command proxy
    
    This isn't perfect by any means, but it does get us a situation where we
    can probably be able to switch the deviced plugin to using IdeRunContext
    as part of the revamp. It just is a helper program that can proxy some
    signals across and tries to cleanup/etc after the program cleanly.

 src/plugins/deviced/gnome-builder-deviced.c | 296 +++++++++++++++++++++++++++-
 1 file changed, 290 insertions(+), 6 deletions(-)
---
diff --git a/src/plugins/deviced/gnome-builder-deviced.c b/src/plugins/deviced/gnome-builder-deviced.c
index ca99473ad..bc2802c62 100644
--- a/src/plugins/deviced/gnome-builder-deviced.c
+++ b/src/plugins/deviced/gnome-builder-deviced.c
@@ -23,18 +23,256 @@
 #include <glib/gi18n.h>
 #include <libdeviced.h>
 #include <stdlib.h>
+#include <signal.h>
+#include <string.h>
 
 static GMainLoop *main_loop;
+static GInetSocketAddress *address;
+static DevdProcessService *procsvc;
+static char *pty_id;
+static char *process_id;
+static struct {
+  gboolean exited;
+  int exit_code;
+  int term_sig;
+} exit_info;
+static guint fail_source;
+static guint signal_source;
+static int signal_to_proxy;
 static char *opt_address;
 static char *opt_app_id;
+static int opt_port;
 static int opt_pty_fd = -1;
+static int opt_timeout_seconds = 10;
 static GOptionEntry options[] = {
   { "address", 0, 0, G_OPTION_ARG_STRING, &opt_address, N_("The device address") },
+  { "port", 0, 0, G_OPTION_ARG_INT, &opt_port, N_("The device port number") },
   { "app-id", 0, 0, G_OPTION_ARG_STRING, &opt_app_id, N_("The application to run") },
   { "pty-fd", 0, 0, G_OPTION_ARG_INT, &opt_pty_fd, N_("A PTY to bidirectionally proxy to the device") },
+  { "timeout", 0, 0, G_OPTION_ARG_INT, &opt_timeout_seconds, N_("Number of seconds to wait for the deviced 
peer to appear") },
   { NULL }
 };
 
+static void
+proxy_signal (int signum)
+{
+  /* We need to be signal handler safe here of course, which means no
+   * allocations, no locks, etc. Basically all we can do is read/write to FDs
+   * or set some variables. So we just set the signal to be proxied and handle
+   * it from the main loop on the next cycle through.
+   */
+  signal_to_proxy = signum;
+}
+
+static struct {
+  int signum;
+  sighandler_t previous;
+} proxied_signals[] = {
+  { SIGHUP },
+  { SIGINT },
+  { SIGQUIT },
+  { SIGUSR1 },
+  { SIGUSR2 },
+  { SIGUSR2 },
+  { SIGTERM },
+#if 0
+/* These signals cannot be handled and therefore cannot be proxied.
+ * To do this, we'd need to create a monitor process that watches
+ * us and sends the signal to the peer. Probably more effort than
+ * it is worth if we're going to drop this and move towards Bonsai
+ * anyway in the future.
+ */
+  { SIGSTOP },
+  { SIGKILL },
+#endif
+};
+
+static void
+setup_signal_handling (void)
+{
+  /* Note: We could use signalfd() here on Linux and do this much
+   * better than spinning our main loop occasionally. But that would
+   * still require porting to other platforms and quite frankly it's
+   * not really worth the effort due to how short the lifespan is of
+   * applications running.
+   */
+  for (guint i = 0; i < G_N_ELEMENTS (proxied_signals); i++)
+    proxied_signals[i].previous = signal (proxied_signals[i].signum, proxy_signal);
+}
+
+static void
+tear_down_signal_handling (void)
+{
+  for (guint i = 0; i < G_N_ELEMENTS (proxied_signals); i++)
+    signal (proxied_signals[i].signum, proxied_signals[i].previous);
+}
+
+static gboolean
+fail_to_connect_cb (gpointer data)
+{
+  g_error ("Failed to locate target device, exiting!");
+  return G_SOURCE_REMOVE;
+}
+
+static void
+destroy_pty_cb (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  DevdProcessService *process = (DevdProcessService *)object;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (DEVD_IS_PROCESS_SERVICE (process));
+
+  if (!devd_process_service_destroy_pty_finish (process, result, &error))
+    g_error ("Failed to destroy PTY: %s", error->message);
+
+  g_clear_pointer (&process_id, g_free);
+
+  tear_down_signal_handling ();
+
+  g_main_loop_quit (main_loop);
+
+  if (exit_info.exited)
+    exit (exit_info.exit_code);
+  else
+    kill (getpid (), exit_info.term_sig);
+}
+
+static void
+wait_for_process_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  DevdProcessService *process = (DevdProcessService *)object;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (DEVD_IS_PROCESS_SERVICE (process));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!devd_process_service_wait_for_process_finish (process,
+                                                     result,
+                                                     &exit_info.exited,
+                                                     &exit_info.exit_code,
+                                                     &exit_info.term_sig,
+                                                     &error))
+    g_error ("Failed to wait for process exit: %s", error->message);
+
+  g_printerr ("Process exited\n");
+
+  /* Clean up our PTY if we can */
+  devd_process_service_destroy_pty_async (process,
+                                          pty_id,
+                                          NULL,
+                                          destroy_pty_cb,
+                                          NULL);
+}
+
+static void
+client_run_app_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  DevdClient *client = (DevdClient *)object;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (DEVD_IS_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!(process_id = devd_client_run_app_finish (client, result, &error)))
+    g_error ("Failed to launch process: %s", error->message);
+
+  setup_signal_handling ();
+
+  devd_process_service_wait_for_process_async (procsvc,
+                                               process_id,
+                                               NULL,
+                                               wait_for_process_cb,
+                                               NULL);
+}
+
+static void
+process_create_pty_cb (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  DevdProcessService *process = (DevdProcessService *)object;
+  g_autoptr(GError) error = NULL;
+  DevdClient *client;
+
+  g_assert (DEVD_IS_PROCESS_SERVICE (process));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!(pty_id = devd_process_service_create_pty_finish (process, result, &error)))
+    g_error ("Failed to create PTY: %s", error->message);
+
+  procsvc = g_object_ref (process);
+  client = devd_service_get_client (DEVD_SERVICE (process));
+
+  devd_client_run_app_async (client,
+                             "flatpak",
+                             opt_app_id,
+                             pty_id,
+                             NULL,
+                             client_run_app_cb,
+                             NULL);
+}
+
+static void
+client_connect_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  DevdClient *client = (DevdClient *)object;
+  g_autoptr(DevdProcessService) process = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (DEVD_IS_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!devd_client_connect_finish (client, result, &error))
+    g_error ("Failed to connect to device: %s", error->message);
+
+  if (!(process = devd_process_service_new (client, &error)))
+    g_error ("Failed to locate process service: %s", error->message);
+
+  g_clear_handle_id (&fail_source, g_source_remove);
+
+  devd_process_service_create_pty_async (process,
+                                         opt_pty_fd,
+                                         NULL,
+                                         process_create_pty_cb,
+                                         NULL);
+}
+
+static gboolean
+inet_socket_address_equal (GSocketAddress *a,
+                           GSocketAddress *b)
+{
+  gsize a_size;
+  gsize b_size;
+  gpointer a_data;
+  gpointer b_data;
+
+  g_assert (G_IS_SOCKET_ADDRESS (a));
+  g_assert (G_IS_SOCKET_ADDRESS (b));
+
+  a_size = g_socket_address_get_native_size (a);
+  b_size = g_socket_address_get_native_size (b);
+
+  if (a_size != b_size)
+    return FALSE;
+
+  a_data = g_alloca0 (a_size);
+  b_data = g_alloca0 (b_size);
+
+  if (!g_socket_address_to_native (a, a_data, a_size, NULL) ||
+      !g_socket_address_to_native (b, b_data, b_size, NULL))
+    return FALSE;
+
+  return memcmp (a_data, b_data, a_size) == 0;
+}
+
 static void
 device_added_cb (DevdBrowser *browser,
                  DevdDevice  *device,
@@ -43,7 +281,25 @@ device_added_cb (DevdBrowser *browser,
   g_assert (DEVD_IS_BROWSER (browser));
   g_assert (DEVD_IS_DEVICE (device));
 
-  g_printerr ("%s added\n", devd_device_get_name (device));
+  if (DEVD_IS_NETWORK_DEVICE (device))
+    {
+      GInetSocketAddress *device_address = devd_network_device_get_address (DEVD_NETWORK_DEVICE (device));
+
+      if (inet_socket_address_equal (G_SOCKET_ADDRESS (address),
+                                     G_SOCKET_ADDRESS (device_address)))
+        {
+          g_autoptr(DevdClient) client = devd_device_create_client (device);
+
+          g_signal_handlers_disconnect_by_func (browser,
+                                                G_CALLBACK (device_added_cb),
+                                                user_data);
+
+          devd_client_connect_async (client,
+                                     NULL,
+                                     client_connect_cb,
+                                     NULL);
+        }
+    }
 }
 
 static void
@@ -54,7 +310,20 @@ device_removed_cb (DevdBrowser *browser,
   g_assert (DEVD_IS_BROWSER (browser));
   g_assert (DEVD_IS_DEVICE (device));
 
-  g_printerr ("%s removed\n", devd_device_get_name (device));
+  if (DEVD_IS_NETWORK_DEVICE (device))
+    {
+      GInetSocketAddress *device_address = devd_network_device_get_address (DEVD_NETWORK_DEVICE (device));
+
+      if (inet_socket_address_equal (G_SOCKET_ADDRESS (address),
+                                     G_SOCKET_ADDRESS (device_address)))
+        {
+          /* We might not have, but avahi says so and we just need to be
+           * extra careful so we don't hang indefinitely.
+           */
+          g_printerr ("lost connection from device\n");
+          exit (EXIT_FAILURE);
+        }
+    }
 }
 
 static void
@@ -73,6 +342,18 @@ load_cb (GObject      *object,
     g_error ("%s", error->message);
 }
 
+static gboolean
+signal_source_cb (gpointer data)
+{
+  if (signal_to_proxy != 0 && procsvc != NULL && process_id != NULL)
+    {
+      devd_process_service_send_signal (procsvc, process_id, signal_to_proxy);
+      signal_to_proxy = 0;
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -91,10 +372,10 @@ main (int   argc,
     }
 
   if (opt_address == NULL || opt_app_id == NULL)
-    {
-      g_printerr ("You must provide --address and --app-id\n");
-      return EXIT_FAILURE;
-    }
+    return EXIT_FAILURE;
+
+  if (!(address = G_INET_SOCKET_ADDRESS (g_inet_socket_address_new_from_string (opt_address, opt_port))))
+    return EXIT_FAILURE;
 
   main_loop = g_main_loop_new (NULL, FALSE);
 
@@ -103,6 +384,9 @@ main (int   argc,
   g_signal_connect (browser, "device-removed", G_CALLBACK (device_removed_cb), NULL);
   devd_browser_load_async (browser, NULL, load_cb, NULL);
 
+  fail_source = g_timeout_add_seconds (opt_timeout_seconds, fail_to_connect_cb, NULL);
+  signal_source = g_timeout_add (500, signal_source_cb, NULL);
+
   g_main_loop_run (main_loop);
 
   return EXIT_SUCCESS;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]