[gnome-session] Systemd: Handle polkit dialog at session end more gracefully



commit 1c84d28345289636aea23acaa24b0bd7660bbe09
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Nov 22 21:24:22 2012 -0500

    Systemd: Handle polkit dialog at session end more gracefully
    
    When there are multiple sessions, we may get a polkit dialog after
    the user confirms the power off dialog. Previously, we didn't handle
    this situation very nicely, since we were already in the EndSession
    phase when calling PowerOff, and there is no way back to the Running
    phase from there, even if the user cancels the polkit dialog.
    
    This commit rearranges things so that we call PowerOff before leaving
    the QueryEndSession phase. To prevent the system from going down
    right away, we take a delay inhibitor, and listen for the
    PrepareForShutdown signal to know when we can safely transition
    into EndSession. If the user cancels the polkit dialog, the PowerOff
    call fails with an AccessDenied error, which we handle and return
    to the Running phase. In order to tell gnome-shell that the session
    is back to running, we call a new Close() method on the EndSessionDialog
    interface.
    
    All of this only works with systemd. The ConsoleKit implementation
    works the same as before.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=688076

 gnome-session/gsm-consolekit.c |   24 ++++++
 gnome-session/gsm-manager.c    |   67 ++++++++++++++++++
 gnome-session/gsm-shell.c      |    9 +++
 gnome-session/gsm-shell.h      |    1 +
 gnome-session/gsm-system.c     |   24 ++++++-
 gnome-session/gsm-system.h     |   11 +++
 gnome-session/gsm-systemd.c    |  151 +++++++++++++++++++++++++++++++++++++++-
 7 files changed, 283 insertions(+), 4 deletions(-)
---
diff --git a/gnome-session/gsm-consolekit.c b/gnome-session/gsm-consolekit.c
index 26f0dd5..2ae20cf 100644
--- a/gnome-session/gsm-consolekit.c
+++ b/gnome-session/gsm-consolekit.c
@@ -58,6 +58,7 @@ struct _GsmConsolekitPrivate
         UpClient        *up_client;
 
         gboolean         is_active;
+        gboolean         restarting;
 };
 
 enum {
@@ -960,6 +961,27 @@ gsm_consolekit_remove_inhibitor (GsmSystem   *system,
 }
 
 static void
+gsm_consolekit_prepare_shutdown (GsmSystem *system,
+                                 gboolean   restart)
+{
+        GsmConsolekit *consolekit = GSM_CONSOLEKIT (system);
+
+        consolekit->priv->restarting = restart;
+        g_signal_emit_by_name (system, "shutdown-prepared", TRUE);
+}
+
+static void
+gsm_consolekit_complete_shutdown (GsmSystem *system)
+{
+        GsmConsolekit *consolekit = GSM_CONSOLEKIT (system);
+
+        if (consolekit->priv->restarting)
+                gsm_consolekit_attempt_restart (system);
+        else
+                gsm_consolekit_attempt_stop (system);
+}
+
+static void
 gsm_consolekit_system_init (GsmSystemInterface *iface)
 {
         iface->can_switch_user = gsm_consolekit_can_switch_user;
@@ -975,6 +997,8 @@ gsm_consolekit_system_init (GsmSystemInterface *iface)
         iface->is_login_session = gsm_consolekit_is_login_session;
         iface->add_inhibitor = gsm_consolekit_add_inhibitor;
         iface->remove_inhibitor = gsm_consolekit_remove_inhibitor;
+        iface->prepare_shutdown = gsm_consolekit_prepare_shutdown;
+        iface->complete_shutdown = gsm_consolekit_complete_shutdown;
 }
 
 GsmConsolekit *
diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c
index 1744706..26392e7 100644
--- a/gnome-session/gsm-manager.c
+++ b/gnome-session/gsm-manager.c
@@ -523,6 +523,9 @@ gsm_manager_quit (GsmManager *manager)
         }
 }
 
+static gboolean do_query_end_session_exit (GsmManager *manager);
+static void     do_end_session_exit       (GsmManager *manager);
+
 static void
 end_phase (GsmManager *manager)
 {
@@ -564,9 +567,12 @@ end_phase (GsmManager *manager)
                 }
                 break;
         case GSM_MANAGER_PHASE_QUERY_END_SESSION:
+                if (!do_query_end_session_exit (manager))
+                        start_next_phase = FALSE;
                 break;
         case GSM_MANAGER_PHASE_END_SESSION:
                 maybe_save_session (manager);
+                do_end_session_exit (manager);
                 break;
         case GSM_MANAGER_PHASE_EXIT:
                 start_next_phase = FALSE;
@@ -4079,3 +4085,64 @@ gsm_manager_is_session_running (GsmManager *manager,
         return TRUE;
 }
 
+static void
+on_shutdown_prepared (GsmSystem  *system,
+                      gboolean    success,
+                      GsmManager *manager)
+{
+        g_debug ("GsmManager: on_shutdown_prepared, success: %d", success);
+        g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager);
+
+        if (success) {
+                /* move to end-session phase */
+                end_phase (manager);
+        } else {
+                disconnect_shell_dialog_signals (manager);
+                gsm_shell_close_end_session_dialog (manager->priv->shell);
+                /* back to running phase */
+                cancel_end_session (manager);
+        }
+}
+
+static gboolean
+do_query_end_session_exit (GsmManager *manager)
+{
+        int action;
+
+        switch (manager->priv->logout_type) {
+        case GSM_MANAGER_LOGOUT_LOGOUT:
+                action = GSM_LOGOUT_ACTION_LOGOUT;
+                break;
+        case GSM_MANAGER_LOGOUT_REBOOT:
+        case GSM_MANAGER_LOGOUT_REBOOT_INTERACT:
+        case GSM_MANAGER_LOGOUT_REBOOT_GDM:
+                action = GSM_LOGOUT_ACTION_REBOOT;
+                break;
+        case GSM_MANAGER_LOGOUT_SHUTDOWN:
+        case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT:
+        case GSM_MANAGER_LOGOUT_SHUTDOWN_GDM:
+                action = GSM_LOGOUT_ACTION_SHUTDOWN;
+                break;
+        default:
+                g_warning ("Unexpected logout type %d in do_query_end_session_exit()",
+                           manager->priv->logout_type);
+                action = GSM_LOGOUT_ACTION_LOGOUT;
+                break;
+        }
+
+        if (action == GSM_LOGOUT_ACTION_REBOOT ||
+            action == GSM_LOGOUT_ACTION_SHUTDOWN) {
+                g_signal_connect (manager->priv->system, "shutdown-prepared",
+                                  G_CALLBACK (on_shutdown_prepared), manager);
+                gsm_system_prepare_shutdown (manager->priv->system, action == GSM_LOGOUT_ACTION_REBOOT);
+                return FALSE; /* don't leave query end session yet */
+        }
+
+        return TRUE; /* go to end session phase */
+}
+
+static void
+do_end_session_exit (GsmManager *manager)
+{
+        gsm_system_complete_shutdown (manager->priv->system);
+}
diff --git a/gnome-session/gsm-shell.c b/gnome-session/gsm-shell.c
index 87ff10b..450ca40 100644
--- a/gnome-session/gsm-shell.c
+++ b/gnome-session/gsm-shell.c
@@ -689,3 +689,12 @@ gsm_shell_open_end_session_dialog (GsmShell *shell,
 
         return TRUE;
 }
+
+void
+gsm_shell_close_end_session_dialog (GsmShell *shell)
+{
+        if (!shell->priv->end_session_dialog_proxy)
+                return;
+
+        dbus_g_proxy_call_no_reply (shell->priv->end_session_dialog_proxy, "Close", G_TYPE_INVALID);
+}
diff --git a/gnome-session/gsm-shell.h b/gnome-session/gsm-shell.h
index 785236a..77c8f03 100644
--- a/gnome-session/gsm-shell.h
+++ b/gnome-session/gsm-shell.h
@@ -82,6 +82,7 @@ gboolean         gsm_shell_is_running         (GsmShell *shell);
 gboolean         gsm_shell_open_end_session_dialog (GsmShell *shell,
                                                     GsmShellEndSessionDialogType type,
                                                     GsmStore *inhibitors);
+void             gsm_shell_close_end_session_dialog (GsmShell *shell);
 
 G_END_DECLS
 
diff --git a/gnome-session/gsm-system.c b/gnome-session/gsm-system.c
index 49a4316..0be1bf7 100644
--- a/gnome-session/gsm-system.c
+++ b/gnome-session/gsm-system.c
@@ -28,7 +28,8 @@
 #include "gsm-systemd.h"
 
 enum {
-        REQUEST_COMPLETED = 0,
+        REQUEST_COMPLETED,
+        SHUTDOWN_PREPARED,
         LAST_SIGNAL
 };
 
@@ -53,6 +54,14 @@ gsm_system_default_init (GsmSystemInterface *iface)
                               NULL, NULL, NULL,
                               G_TYPE_NONE,
                               1, G_TYPE_POINTER);
+        signals[SHUTDOWN_PREPARED] =
+                 g_signal_new ("shutdown-prepared",
+                               GSM_TYPE_SYSTEM,
+                               G_SIGNAL_RUN_LAST,
+                               G_STRUCT_OFFSET (GsmSystemInterface, shutdown_prepared),
+                               NULL, NULL, NULL,
+                               G_TYPE_NONE,
+                               1, G_TYPE_BOOLEAN);
         pspec = g_param_spec_boolean ("active",
                                       "Active",
                                       "Whether or not session is active",
@@ -169,6 +178,19 @@ gsm_system_is_active (GsmSystem *system)
         return is_active;
 }
 
+void
+gsm_system_prepare_shutdown  (GsmSystem *system,
+                              gboolean   restart)
+{
+        GSM_SYSTEM_GET_IFACE (system)->prepare_shutdown (system, restart);
+}
+
+void
+gsm_system_complete_shutdown (GsmSystem *system)
+{
+        GSM_SYSTEM_GET_IFACE (system)->complete_shutdown (system);
+}
+
 GsmSystem *
 gsm_get_system (void)
 {
diff --git a/gnome-session/gsm-system.h b/gnome-session/gsm-system.h
index 6941823..c1c0d76 100644
--- a/gnome-session/gsm-system.h
+++ b/gnome-session/gsm-system.h
@@ -49,6 +49,9 @@ struct _GsmSystemInterface
         void (* request_completed)    (GsmSystem *system,
                                        GError    *error);
 
+        void (* shutdown_prepared)    (GsmSystem *system,
+                                       gboolean   success);
+
         gboolean (* can_switch_user)  (GsmSystem *system);
         gboolean (* can_stop)         (GsmSystem *system);
         gboolean (* can_restart)      (GsmSystem *system);
@@ -66,6 +69,9 @@ struct _GsmSystemInterface
                                        GsmInhibitorFlag  flags);
         void     (* remove_inhibitor) (GsmSystem        *system,
                                        const gchar      *id);
+        void     (* prepare_shutdown) (GsmSystem   *system,
+                                       gboolean     restart);
+        void     (* complete_shutdown)(GsmSystem   *system);
 };
 
 enum _GsmSystemError {
@@ -110,6 +116,11 @@ void       gsm_system_add_inhibitor    (GsmSystem        *system,
 
 void       gsm_system_remove_inhibitor (GsmSystem        *system,
                                         const gchar      *id);
+void       gsm_system_prepare_shutdown  (GsmSystem  *system,
+                                         gboolean    restart);
+void       gsm_system_complete_shutdown (GsmSystem  *system);
+
+
 
 G_END_DECLS
 
diff --git a/gnome-session/gsm-systemd.c b/gnome-session/gsm-systemd.c
index 98e9f27..68d8988 100644
--- a/gnome-session/gsm-systemd.c
+++ b/gnome-session/gsm-systemd.c
@@ -60,6 +60,9 @@ struct _GsmSystemdPrivate
         gint             inhibit_fd;
 
         gboolean         is_active;
+
+        gint             delay_inhibit_fd;
+        gboolean         prepare_for_shutdown_expected;
 };
 
 enum {
@@ -77,13 +80,23 @@ static void
 drop_system_inhibitor (GsmSystemd *manager)
 {
         if (manager->priv->inhibit_fd != -1) {
-                g_debug ("Dropping system inhibitor");
+                g_debug ("GsmSystemd: Dropping system inhibitor");
                 close (manager->priv->inhibit_fd);
                 manager->priv->inhibit_fd = -1;
         }
 }
 
 static void
+drop_delay_inhibitor (GsmSystemd *manager)
+{
+        if (manager->priv->delay_inhibit_fd != -1) {
+                g_debug ("GsmSystemd: Dropping delay inhibitor");
+                close (manager->priv->delay_inhibit_fd);
+                manager->priv->delay_inhibit_fd = -1;
+        }
+}
+
+static void
 gsm_systemd_finalize (GObject *object)
 {
         GsmSystemd *systemd = GSM_SYSTEMD (object);
@@ -100,6 +113,7 @@ gsm_systemd_finalize (GObject *object)
                 g_slist_free_full (systemd->priv->inhibitors, g_free);
         }
         drop_system_inhibitor (systemd);
+        drop_delay_inhibitor (systemd);
 
         G_OBJECT_CLASS (gsm_systemd_parent_class)->finalize (object);
 }
@@ -247,10 +261,16 @@ on_sd_source_changed (gpointer user_data)
                 self->priv->is_active = active;
                 g_object_notify (G_OBJECT (self), "active");
         }
-        
+
         return TRUE;
 }
 
+static void sd_proxy_signal_cb (GDBusProxy  *proxy,
+                                const gchar *sender_name,
+                                const gchar *signal_name,
+                                GVariant    *parameters,
+                                gpointer     user_data);
+
 static void
 gsm_systemd_init (GsmSystemd *manager)
 {
@@ -263,6 +283,7 @@ gsm_systemd_init (GsmSystemd *manager)
                                                      GsmSystemdPrivate);
 
         manager->priv->inhibit_fd = -1;
+        manager->priv->delay_inhibit_fd = -1;
 
         bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
         if (bus == NULL)
@@ -283,6 +304,8 @@ gsm_systemd_init (GsmSystemd *manager)
                 g_clear_error (&error);
         }
 
+        g_signal_connect (manager->priv->sd_proxy, "g-signal",
+                          G_CALLBACK (sd_proxy_signal_cb), manager);
 
         sd_pid_get_session (getpid (), &manager->priv->session_id);
 
@@ -532,7 +555,7 @@ gsm_systemd_is_login_session (GsmSystem *system)
 
         res = sd_session_get_class (manager->priv->session_id, &session_class);
         if (res < 0) {
-                g_warning ("could not get session class: %s", strerror (-res));
+                g_warning ("Could not get session class: %s", strerror (-res));
                 return FALSE;
         }
         ret = (g_strcmp0 (session_class, "greeter") == 0);
@@ -743,6 +766,95 @@ gsm_systemd_remove_inhibitor (GsmSystem   *system,
 }
 
 static void
+reboot_or_poweroff_done (GObject      *source,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+        GsmSystemd *systemd = user_data;
+        GVariant *result;
+        GError *error = NULL;
+
+        result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+                                           res,
+                                           &error);
+
+        if (result == NULL) {
+                if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED)) {
+                        g_warning ("Shutdown failed: %s", error->message);
+                }
+                g_error_free (error);
+                drop_delay_inhibitor (systemd);
+                g_debug ("GsmSystemd: shutdown preparation failed");
+                systemd->priv->prepare_for_shutdown_expected = FALSE;
+                g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+        } else {
+                g_variant_unref (result);
+        }
+}
+
+static void
+gsm_systemd_prepare_shutdown (GsmSystem *system,
+                              gboolean   restart)
+{
+        GsmSystemd *systemd = GSM_SYSTEMD (system);
+        GUnixFDList *fd_list;
+        GVariant *res;
+        GError *error = NULL;
+        gint idx;
+
+        g_debug ("GsmSystemd: prepare shutdown");
+
+        res = g_dbus_proxy_call_with_unix_fd_list_sync (systemd->priv->sd_proxy,
+                                                        "Inhibit",
+                                                        g_variant_new ("(ssss)",
+                                                                       "shutdown",
+                                                                       g_get_user_name (),
+                                                                       "Preparing to end the session",
+                                                                       "delay"),
+                                                        0,
+                                                        G_MAXINT,
+                                                        NULL,
+                                                        &fd_list,
+                                                        NULL,
+                                                        &error);
+        if (res == NULL) {
+                g_warning ("Failed to get delay inhibitor: %s", error->message);
+                g_error_free (error);
+                g_signal_emit_by_name (systemd, "shutdown-prepared", FALSE);
+                return;
+        }
+
+        g_variant_get (res, "(h)", &idx);
+
+        systemd->priv->delay_inhibit_fd = g_unix_fd_list_get (fd_list, idx, NULL);
+
+        g_debug ("GsmSystemd: got delay inhibitor, fd = %d", systemd->priv->delay_inhibit_fd);
+
+        g_variant_unref (res);
+        g_object_unref (fd_list);
+
+        systemd->priv->prepare_for_shutdown_expected = TRUE;
+
+        g_dbus_proxy_call (systemd->priv->sd_proxy,
+                           restart ? "Reboot" : "PowerOff",
+                           g_variant_new ("(b)", TRUE),
+                           0,
+                           G_MAXINT,
+                           NULL,
+                           reboot_or_poweroff_done,
+                           systemd);
+}
+
+static void
+gsm_systemd_complete_shutdown (GsmSystem *system)
+{
+        GsmSystemd *systemd = GSM_SYSTEMD (system);
+
+        /* remove delay inhibitor, if any */
+        drop_delay_inhibitor (systemd);
+}
+
+static void
 gsm_systemd_system_init (GsmSystemInterface *iface)
 {
         iface->can_switch_user = gsm_systemd_can_switch_user;
@@ -758,6 +870,8 @@ gsm_systemd_system_init (GsmSystemInterface *iface)
         iface->is_login_session = gsm_systemd_is_login_session;
         iface->add_inhibitor = gsm_systemd_add_inhibitor;
         iface->remove_inhibitor = gsm_systemd_remove_inhibitor;
+        iface->prepare_shutdown = gsm_systemd_prepare_shutdown;
+        iface->complete_shutdown = gsm_systemd_complete_shutdown;
 }
 
 GsmSystemd *
@@ -773,6 +887,36 @@ gsm_systemd_new (void)
         return manager;
 }
 
+static void
+sd_proxy_signal_cb (GDBusProxy  *proxy,
+                    const gchar *sender_name,
+                    const gchar *signal_name,
+                    GVariant    *parameters,
+                    gpointer     user_data)
+{
+        GsmSystemd *systemd = user_data;
+        gboolean is_about_to_shutdown;
+
+        g_debug ("GsmSystemd: received logind signal: %s", signal_name);
+
+        if (g_strcmp0 (signal_name, "PrepareForShutdown") != 0) {
+                g_debug ("GsmSystemd: ignoring %s signal", signal_name);
+                return;
+        }
+
+        g_variant_get (parameters, "(b)", &is_about_to_shutdown);
+        if (!is_about_to_shutdown) {
+                g_debug ("GsmSystemd: ignoring %s signal since about-to-shutdown is FALSE", signal_name);
+                return;
+        }
+
+        if (systemd->priv->prepare_for_shutdown_expected) {
+                g_debug ("GsmSystemd: shutdown successfully prepared");
+                g_signal_emit_by_name (systemd, "shutdown-prepared", TRUE);
+                systemd->priv->prepare_for_shutdown_expected = FALSE;
+        }
+}
+
 #else
 
 GsmSystemd *
@@ -782,3 +926,4 @@ gsm_systemd_new (void)
 }
 
 #endif
+



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