Blob Blame History Raw
From bef4aa0d07924e22c0689d5308802b576d756e19 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Fri, 24 Mar 2023 12:16:46 -0400
Subject: [PATCH] daemon: Fix boot delay

commit 836a9135fe2d8fdc7d6de3a6d11fb9a5fd05f926 made accountsservice
reload wtmp less aggressively.

Unfortunately, if wtmp is modified during boot, that added latency
and delay the user list getting loaded.

This commit addresses the problem by tracking how pressing the queued
reload operation is, and makes it happen sooner if a higher priority
request comes in.
---
 src/daemon.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/daemon.c b/src/daemon.c
index 151f294..9209976 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -43,79 +43,89 @@
 #include <glib-object.h>
 #include <glib/gstdio.h>
 #include <gio/gio.h>
 #include <polkit/polkit.h>
 
 #include "user-classify.h"
 #include "wtmp-helper.h"
 #include "daemon.h"
 #include "util.h"
 #include "user.h"
 #include "accounts-user-generated.h"
 
 #define PATH_PASSWD "passwd"
 #define PATH_SHADOW "shadow"
 #define PATH_GROUP "/etc/group"
 #define PATH_DM     "/etc/systemd/system/display-manager.service"
 
 enum
 {
         PROP_0,
         PROP_DAEMON_VERSION
 };
 
 typedef enum
 {
         DISPLAY_MANAGER_TYPE_NONE = -1,
         DISPLAY_MANAGER_TYPE_GDM,
         DISPLAY_MANAGER_TYPE_LIGHTDM
 } DisplayManagerType;
 
+typedef enum
+{
+        USER_RELOAD_TYPE_NONE = 0,
+        USER_RELOAD_TYPE_IMMEDIATELY,
+        USER_RELOAD_TYPE_SOON,
+        USER_RELOAD_TYPE_EVENTUALLY
+} UserReloadType;
+
 typedef struct
 {
         GDBusConnection *bus_connection;
 
         GHashTable      *users;
         gsize            number_of_normal_users;
         GList           *explicitly_requested_users;
 
         User            *autologin;
 
         GFileMonitor    *passwd_monitor;
         GFileMonitor    *shadow_monitor;
         GFileMonitor    *group_monitor;
         GFileMonitor    *dm_monitor;
         GFileMonitor    *wtmp_monitor;
 
         GQueue          *pending_list_cached_users;
 
+        UserReloadType   reload_type;
         guint            reload_id;
+
         guint            autologin_id;
 
         PolkitAuthority *authority;
         GHashTable      *extension_ifaces;
 } DaemonPrivate;
 
 typedef struct passwd * (* EntryGeneratorFunc) (Daemon *,
                                                 GHashTable *,
                                                 GHashTable *,
                                                 gpointer *,
                                                 struct spwd **shadow_entry);
 
 typedef struct
 {
         Daemon                *daemon;
         GDBusMethodInvocation *context;
 } ListUserData;
 
 static void finish_list_cached_users (ListUserData *data);
 
 static void list_user_data_free (ListUserData *data);
 
 static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (Daemon, daemon, ACCOUNTS_TYPE_ACCOUNTS_SKELETON, G_ADD_PRIVATE (Daemon) G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_ACCOUNTS, daemon_accounts_accounts_iface_init));
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (Daemon, g_object_unref)
 
 static const GDBusErrorEntry accounts_error_entries[] =
 {
@@ -598,60 +608,61 @@ reload_users (Daemon *daemon)
                         user_unregister (user);
                 }
         }
 
         /* Register all the new users */
         g_hash_table_iter_init (&iter, users);
         while (g_hash_table_iter_next (&iter, &name, &value)) {
                 User *user = value;
                 User *stale_user;
 
                 stale_user = g_hash_table_lookup (old_users, name);
 
                 if (!stale_user || (!user_get_cached (stale_user) && user_get_cached (user))) {
                         user_register (user);
                         accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon),
                                                            user_get_object_path (user));
                 }
                 g_object_thaw_notify (G_OBJECT (user));
         }
 
         g_hash_table_destroy (old_users);
 }
 
 static gboolean
 reload_users_timeout (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
         reload_users (daemon);
         priv->reload_id = 0;
+        priv->reload_type = USER_RELOAD_TYPE_NONE;
 
         g_queue_foreach (priv->pending_list_cached_users,
                          (GFunc) finish_list_cached_users, NULL);
         g_queue_clear (priv->pending_list_cached_users);
 
         return FALSE;
 }
 
 static gboolean load_autologin (Daemon   *daemon,
                                 gchar   **name,
                                 gboolean *enabled,
                                 GError  **error);
 
 static gboolean
 reload_autologin_timeout (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
         AccountsAccounts *accounts = ACCOUNTS_ACCOUNTS (daemon);
         gboolean enabled;
         g_autofree gchar *name = NULL;
 
         g_autoptr (GError) error = NULL;
         User *user = NULL;
 
         priv->autologin_id = 0;
 
         if (!load_autologin (daemon, &name, &enabled, &error)) {
                 g_debug ("failed to load autologin conf file: %s", error->message);
                 return FALSE;
         }
@@ -671,87 +682,100 @@ reload_autologin_timeout (Daemon *daemon)
                 users[0] = user_get_object_path (user);
                 users[1] = NULL;
                 accounts_accounts_set_automatic_login_users (accounts, users);
                 if (priv->autologin != user) {
                         g_object_set (user, "automatic-login", TRUE, NULL);
                         priv->autologin = g_object_ref (user);
                         g_signal_emit_by_name (priv->autologin, "changed", 0);
                 }
         } else {
                 g_debug ("automatic login is disabled");
                 accounts_accounts_set_automatic_login_users (accounts, NULL);
         }
 
         return FALSE;
 }
 
 static void
 queue_reload_users_eventually (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
         if (priv->reload_id > 0) {
                 return;
         }
 
         /* we wait 10 seconds before reloading the users, so e.g. wtmp
          * parsing doesn't hammer the cpu if the user is logging in
          * and out in a continuous loop.
          */
         priv->reload_id = g_timeout_add_seconds (10, (GSourceFunc) reload_users_timeout, daemon);
+        priv->reload_type = USER_RELOAD_TYPE_EVENTUALLY;
 }
 
 static void
 queue_reload_users_soon (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
+        if (priv->reload_type > USER_RELOAD_TYPE_SOON) {
+            g_source_remove (priv->reload_id);
+            priv->reload_id = 0;
+        }
+
         if (priv->reload_id > 0) {
                 return;
         }
 
         /* we wait half a second or so in case /etc/passwd and
          * /etc/shadow are changed at the same time, or repeatedly.
          */
         priv->reload_id = g_timeout_add (500, (GSourceFunc) reload_users_timeout, daemon);
+        priv->reload_type = USER_RELOAD_TYPE_SOON;
 }
 
 static void
 queue_reload_users (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
+        if (priv->reload_type > USER_RELOAD_TYPE_IMMEDIATELY) {
+            g_source_remove (priv->reload_id);
+            priv->reload_id = 0;
+        }
+
         if (priv->reload_id > 0) {
                 return;
         }
 
         priv->reload_id = g_idle_add ((GSourceFunc) reload_users_timeout, daemon);
+        priv->reload_type = USER_RELOAD_TYPE_IMMEDIATELY;
 }
 
 static void
 queue_reload_autologin (Daemon *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
         if (priv->autologin_id > 0) {
                 return;
         }
 
         priv->autologin_id = g_idle_add ((GSourceFunc) reload_autologin_timeout, daemon);
 }
 
 static void
 on_users_monitor_changed (GFileMonitor     *monitor,
                           GFile            *file,
                           GFile            *other_file,
                           GFileMonitorEvent event_type,
                           Daemon           *daemon)
 {
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
 
         if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
             event_type != G_FILE_MONITOR_EVENT_CREATED) {
                 return;
         }
 
         if (monitor == priv->wtmp_monitor) {
                 queue_reload_users_eventually (daemon);
@@ -1120,60 +1144,61 @@ finish_list_cached_users (ListUserData *data)
                 }
 
                 if (!user_get_cached (user)) {
                         g_debug ("user %s %ld not cached", name, (long) uid);
                         continue;
                 }
 
                 g_debug ("user %s %ld not excluded", name, (long) uid);
                 g_ptr_array_add (object_paths, (gpointer) user_get_object_path (user));
         }
         g_ptr_array_add (object_paths, NULL);
 
         accounts_accounts_complete_list_cached_users (NULL, data->context, (const gchar * const *) object_paths->pdata);
 
         list_user_data_free (data);
 }
 
 static gboolean
 daemon_list_cached_users (AccountsAccounts      *accounts,
                           GDBusMethodInvocation *context)
 {
         Daemon *daemon = (Daemon *) accounts;
         DaemonPrivate *priv = daemon_get_instance_private (daemon);
         ListUserData *data;
 
         data = list_user_data_new (daemon, context);
 
         if (priv->reload_id > 0) {
                 /* reload pending -- finish call in reload_users_timeout */
                 g_queue_push_tail (priv->pending_list_cached_users, data);
+                queue_reload_users (daemon);
         } else {
                 finish_list_cached_users (data);
         }
 
         return TRUE;
 }
 
 static int
 sort_languages (gconstpointer element_1,
                 gconstpointer element_2,
                 GHashTable   *language_frequency_map)
 {
         const char *language_1 = *(const char **) element_1;
         const char *language_2 = *(const char **) element_2;
         int count_1, count_2;
 
         count_1 = GPOINTER_TO_INT (g_hash_table_lookup (language_frequency_map, language_1));
         count_2 = GPOINTER_TO_INT (g_hash_table_lookup (language_frequency_map, language_2));
 
         if (count_2 == count_1) {
                 return strcmp (language_1, language_2);
         }
 
         return count_2 - count_1;
 }
 
 static gboolean
 daemon_get_users_languages (AccountsAccounts      *accounts,
                             GDBusMethodInvocation *context)
 {
-- 
2.39.2