Blob Blame History Raw
From 08f5f88ca6fb0edfc94af4c85912484b6048691b Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Thu, 2 Aug 2018 15:06:09 -0400
Subject: [PATCH 14/51] daemon: kill and restart greeter on demand under
 wayland

Right now we leave the greeter alive after the user logs in.
This is for two reasons:

1) When the greeter is running Xorg, there's no way to kill
it when it's running on an inactive VT (X jumps to the foreground
when being killed)

2) The greeter, in a way, provides a securepath for unlock.
Users in theory could know that by hitting ctrl-alt-f1 to secure
attention, the login screen presented is not spoofed.

Since we use wayland by default, 1 isn't that much of a concern,
and 2 is a bit of niche feature that most users probably haven't
considered.

And there's a huge downside to keeping the greeter alive: it uses
a very large amount of memory.

This commit changes GDM to kill the login screen when switching
away from the login screen's VT and restarting it when switching
back.

Based heavily on work by Hans de Goede <hdegoede@redhat.com>

Closes: https://gitlab.gnome.org/GNOME/gdm/issues/222
---
 daemon/gdm-local-display-factory.c | 167 +++++++++++++++++++++++++++++
 1 file changed, 167 insertions(+)

diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c
index 7f7735ca1..4ae656ab3 100644
--- a/daemon/gdm-local-display-factory.c
+++ b/daemon/gdm-local-display-factory.c
@@ -34,60 +34,65 @@
 #include "gdm-manager.h"
 #include "gdm-display-factory.h"
 #include "gdm-local-display-factory.h"
 #include "gdm-local-display-factory-glue.h"
 
 #include "gdm-settings-keys.h"
 #include "gdm-settings-direct.h"
 #include "gdm-display-store.h"
 #include "gdm-local-display.h"
 #include "gdm-legacy-display.h"
 
 #define GDM_LOCAL_DISPLAY_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_LOCAL_DISPLAY_FACTORY, GdmLocalDisplayFactoryPrivate))
 
 #define GDM_DBUS_PATH                       "/org/gnome/DisplayManager"
 #define GDM_LOCAL_DISPLAY_FACTORY_DBUS_PATH GDM_DBUS_PATH "/LocalDisplayFactory"
 #define GDM_MANAGER_DBUS_NAME               "org.gnome.DisplayManager.LocalDisplayFactory"
 
 #define MAX_DISPLAY_FAILURES 5
 
 struct GdmLocalDisplayFactoryPrivate
 {
         GdmDBusLocalDisplayFactory *skeleton;
         GDBusConnection *connection;
         GHashTable      *used_display_numbers;
 
         /* FIXME: this needs to be per seat? */
         guint            num_failures;
 
         guint            seat_new_id;
         guint            seat_removed_id;
+
+#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
+        char            *tty_of_active_vt;
+        guint            active_vt_watch_id;
+#endif
 };
 
 enum {
         PROP_0,
 };
 
 static void     gdm_local_display_factory_class_init    (GdmLocalDisplayFactoryClass *klass);
 static void     gdm_local_display_factory_init          (GdmLocalDisplayFactory      *factory);
 static void     gdm_local_display_factory_finalize      (GObject                     *object);
 
 static GdmDisplay *create_display                       (GdmLocalDisplayFactory      *factory,
                                                          const char                  *seat_id,
                                                          const char                  *session_type,
                                                          gboolean                    initial_display);
 
 static void     on_display_status_changed               (GdmDisplay                  *display,
                                                          GParamSpec                  *arg1,
                                                          GdmLocalDisplayFactory      *factory);
 
 static gboolean gdm_local_display_factory_sync_seats    (GdmLocalDisplayFactory *factory);
 static gpointer local_display_factory_object = NULL;
 
 G_DEFINE_TYPE (GdmLocalDisplayFactory, gdm_local_display_factory, GDM_TYPE_DISPLAY_FACTORY)
 
 GQuark
 gdm_local_display_factory_error_quark (void)
 {
         static GQuark ret = 0;
         if (ret == 0) {
                 ret = g_quark_from_static_string ("gdm_local_display_factory_error");
@@ -507,98 +512,260 @@ gdm_local_display_factory_sync_seats (GdmLocalDisplayFactory *factory)
 static void
 on_seat_new (GDBusConnection *connection,
              const gchar     *sender_name,
              const gchar     *object_path,
              const gchar     *interface_name,
              const gchar     *signal_name,
              GVariant        *parameters,
              gpointer         user_data)
 {
         const char *seat;
 
         g_variant_get (parameters, "(&s&o)", &seat, NULL);
         create_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat, NULL, FALSE);
 }
 
 static void
 on_seat_removed (GDBusConnection *connection,
                  const gchar     *sender_name,
                  const gchar     *object_path,
                  const gchar     *interface_name,
                  const gchar     *signal_name,
                  GVariant        *parameters,
                  gpointer         user_data)
 {
         const char *seat;
 
         g_variant_get (parameters, "(&s&o)", &seat, NULL);
         delete_display (GDM_LOCAL_DISPLAY_FACTORY (user_data), seat);
 }
 
+#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
+static gboolean
+lookup_by_session_id (const char *id,
+                      GdmDisplay *display,
+                      gpointer    user_data)
+{
+        const char *looking_for = user_data;
+        const char *current;
+
+        current = gdm_display_get_session_id (display);
+        return g_strcmp0 (current, looking_for) == 0;
+}
+
+static void
+maybe_stop_greeter_display (GdmDisplay *display)
+{
+        g_autofree char *display_session_type = NULL;
+
+        if (gdm_display_get_status (display) != GDM_DISPLAY_MANAGED)
+                return;
+
+        g_object_get (G_OBJECT (display),
+                      "session-type", &display_session_type,
+                      NULL);
+
+        /* we can only stop greeter for wayland sessions, since
+         * X server would jump back on exit */
+        if (g_strcmp0 (display_session_type, "wayland") != 0)
+                return;
+
+        gdm_display_stop_greeter_session (display);
+        gdm_display_unmanage (display);
+        gdm_display_finish (display);
+}
+
+static gboolean
+on_vt_changed (GIOChannel    *source,
+               GIOCondition   condition,
+               GdmLocalDisplayFactory *factory)
+{
+        GIOStatus status;
+        static const char *tty_of_initial_vt = "tty" GDM_INITIAL_VT;
+        g_autofree char *tty_of_previous_vt = NULL;
+        g_autofree char *tty_of_active_vt = NULL;
+        g_autofree char *login_session_id = NULL;
+        g_autofree char *active_session_id = NULL;
+        const char *session_type = NULL;
+        int ret;
+
+        g_io_channel_seek_position (source, 0, G_SEEK_SET, NULL);
+
+        if (condition & G_IO_PRI) {
+                g_autoptr (GError) error = NULL;
+                status = g_io_channel_read_line (source, &tty_of_active_vt, NULL, NULL, &error);
+
+                if (error != NULL) {
+                        g_warning ("could not read active VT from kernel: %s", error->message);
+                }
+                switch (status) {
+                        case G_IO_STATUS_ERROR:
+                            return G_SOURCE_REMOVE;
+                        case G_IO_STATUS_EOF:
+                            return G_SOURCE_REMOVE;
+                        case G_IO_STATUS_AGAIN:
+                            return G_SOURCE_CONTINUE;
+                        case G_IO_STATUS_NORMAL:
+                            break;
+                }
+        }
+
+        if ((condition & G_IO_ERR) || (condition & G_IO_HUP))
+                return G_SOURCE_REMOVE;
+
+        if (tty_of_active_vt == NULL)
+                return G_SOURCE_CONTINUE;
+
+        g_strchomp (tty_of_active_vt);
+
+        /* don't do anything if we're on the same VT we were before */
+        if (g_strcmp0 (tty_of_active_vt, factory->priv->tty_of_active_vt) == 0)
+                return G_SOURCE_CONTINUE;
+
+        tty_of_previous_vt = g_steal_pointer (&factory->priv->tty_of_active_vt);
+        factory->priv->tty_of_active_vt = g_steal_pointer (&tty_of_active_vt);
+
+        /* if the old VT was running a wayland login screen kill it
+         */
+        if (gdm_get_login_window_session_id ("seat0", &login_session_id)) {
+                unsigned int vt;
+
+                ret = sd_session_get_vt (login_session_id, &vt);
+                if (ret == 0 && vt != 0) {
+                        g_autofree char *tty_of_login_window_vt = NULL;
+
+                        tty_of_login_window_vt = g_strdup_printf ("tty%u", vt);
+
+                        if (g_strcmp0 (tty_of_login_window_vt, tty_of_previous_vt) == 0) {
+                                GdmDisplayStore *store;
+                                GdmDisplay *display;
+
+                                store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory));
+                                display = gdm_display_store_find (store,
+                                                                  lookup_by_session_id,
+                                                                  (gpointer) login_session_id);
+
+                                if (display != NULL)
+                                        maybe_stop_greeter_display (display);
+                        }
+                }
+        }
+
+        /* if user jumped back to initial vt and it's empty put a login screen
+         * on it (unless a login screen is already running elsewhere, then
+         * jump to that login screen)
+         */
+        if (strcmp (factory->priv->tty_of_active_vt, tty_of_initial_vt) != 0) {
+                return G_SOURCE_CONTINUE;
+        }
+
+        ret = sd_seat_get_active ("seat0", &active_session_id, NULL);
+
+        if (ret == 0) {
+                g_autofree char *state = NULL;
+                ret = sd_session_get_state (active_session_id, &state);
+
+                /* if there's something already running on the active VT then bail */
+                if (ret == 0 && g_strcmp0 (state, "closing") != 0)
+                        return G_SOURCE_CONTINUE;
+        }
+
+        if (gdm_local_display_factory_use_wayland ())
+                session_type = "wayland";
+
+        create_display (factory, "seat0", session_type, TRUE);
+
+        return G_SOURCE_CONTINUE;
+}
+#endif
+
 static void
 gdm_local_display_factory_start_monitor (GdmLocalDisplayFactory *factory)
 {
+        g_autoptr (GIOChannel) io_channel = NULL;
+
         factory->priv->seat_new_id = g_dbus_connection_signal_subscribe (factory->priv->connection,
                                                                          "org.freedesktop.login1",
                                                                          "org.freedesktop.login1.Manager",
                                                                          "SeatNew",
                                                                          "/org/freedesktop/login1",
                                                                          NULL,
                                                                          G_DBUS_SIGNAL_FLAGS_NONE,
                                                                          on_seat_new,
                                                                          g_object_ref (factory),
                                                                          g_object_unref);
         factory->priv->seat_removed_id = g_dbus_connection_signal_subscribe (factory->priv->connection,
                                                                              "org.freedesktop.login1",
                                                                              "org.freedesktop.login1.Manager",
                                                                              "SeatRemoved",
                                                                              "/org/freedesktop/login1",
                                                                              NULL,
                                                                              G_DBUS_SIGNAL_FLAGS_NONE,
                                                                              on_seat_removed,
                                                                              g_object_ref (factory),
                                                                              g_object_unref);
+
+#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
+        io_channel = g_io_channel_new_file ("/sys/class/tty/tty0/active", "r", NULL);
+
+        if (io_channel != NULL) {
+                factory->priv->active_vt_watch_id =
+                        g_io_add_watch (io_channel,
+                                        G_IO_PRI,
+                                        (GIOFunc)
+                                        on_vt_changed,
+                                        factory);
+        }
+#endif
 }
 
 static void
 gdm_local_display_factory_stop_monitor (GdmLocalDisplayFactory *factory)
 {
         if (factory->priv->seat_new_id) {
                 g_dbus_connection_signal_unsubscribe (factory->priv->connection,
                                                       factory->priv->seat_new_id);
                 factory->priv->seat_new_id = 0;
         }
         if (factory->priv->seat_removed_id) {
                 g_dbus_connection_signal_unsubscribe (factory->priv->connection,
                                                       factory->priv->seat_removed_id);
                 factory->priv->seat_removed_id = 0;
         }
+#if defined(ENABLE_WAYLAND_SUPPORT) && defined(ENABLE_USER_DISPLAY_SERVER)
+        if (factory->priv->active_vt_watch_id) {
+                g_source_remove (factory->priv->active_vt_watch_id);
+                factory->priv->active_vt_watch_id = 0;
+        }
+
+        g_clear_pointer (&factory->priv->tty_of_active_vt, g_free);
+#endif
 }
 
 static void
 on_display_added (GdmDisplayStore        *display_store,
                   const char             *id,
                   GdmLocalDisplayFactory *factory)
 {
         GdmDisplay *display;
 
         display = gdm_display_store_lookup (display_store, id);
 
         if (display != NULL) {
                 g_signal_connect_object (display, "notify::status",
                                          G_CALLBACK (on_display_status_changed),
                                          factory,
                                          0);
 
                 g_object_weak_ref (G_OBJECT (display), (GWeakNotify)on_display_disposed, factory);
         }
 }
 
 static void
 on_display_removed (GdmDisplayStore        *display_store,
                     GdmDisplay             *display,
                     GdmLocalDisplayFactory *factory)
 {
         g_signal_handlers_disconnect_by_func (display, G_CALLBACK (on_display_status_changed), factory);
         g_object_weak_unref (G_OBJECT (display), (GWeakNotify)on_display_disposed, factory);
 }
 
-- 
2.27.0