]> Savannah Git Hosting - gnulib.git/commitdiff
readutmp: Fix boot time in VMs after sleep state and date update.
authorBruno Haible <bruno@clisp.org>
Wed, 9 Aug 2023 19:12:40 +0000 (21:12 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 9 Aug 2023 19:12:40 +0000 (21:12 +0200)
* lib/readutmp.c (read_utmp_from_file): New function, extracted from
read_utmp.
(get_boot_time_uncached): Before all other approaches, try to find the
boot time in the /var/run/utmp file.
(read_utmp): Invoke read_utmp_from_file.

ChangeLog
lib/readutmp.c

index c2c961364389ff500d3e3cb77b8bc6e3edfb3c27..3725c3bf9ee3108543e3a89b39780fc0ac9ae937 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2023-08-09  Bruno Haible  <bruno@clisp.org>
 
+       readutmp: Fix boot time in VMs after sleep state and date update.
+       * lib/readutmp.c (read_utmp_from_file): New function, extracted from
+       read_utmp.
+       (get_boot_time_uncached): Before all other approaches, try to find the
+       boot time in the /var/run/utmp file.
+       (read_utmp): Invoke read_utmp_from_file.
+
        readutmp: Make it easier to filter for/against the boot-time entry.
        * lib/readutmp.h (READ_UTMP_BOOT_TIME, READ_UTMP_NO_BOOT_TIME): New
        enum items.
index a7773c4f36aa3154da3c6c632ce5d612f6a52723..f7a839a1ebe2591f85e2e71a3968e1b9eab0b15e 100644 (file)
@@ -281,12 +281,211 @@ finish_utmp (struct utmp_alloc a)
   return a;
 }
 
+# if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION && !HAVE_DECL_GETUTENT
+struct utmp *getutent (void);
+# endif
+
+static int
+read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
+                     int options)
+{
+  if ((options & READ_UTMP_BOOT_TIME) != 0
+      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
+    {
+      /* No entries can match the given options.  */
+      *n_entries = 0;
+      *utmp_buf = NULL;
+      return 0;
+    }
+
+  struct utmp_alloc a = {0};
+
+# if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
+
+  /* Ignore the return value for now.
+     Solaris' utmpname returns 1 upon success -- which is contrary
+     to what the GNU libc version does.  In addition, older GNU libc
+     versions are actually void.   */
+  UTMP_NAME_FUNCTION ((char *) file);
+
+  SET_UTMP_ENT ();
+
+  void const *entry;
+
+  while ((entry = GET_UTMP_ENT ()) != NULL)
+    {
+#  if __GLIBC__ && _TIME_BITS == 64
+      /* This is a near-copy of glibc's struct utmpx, which stops working
+         after the year 2038.  Unlike the glibc version, struct utmpx32
+         describes the file format even if time_t is 64 bits.  */
+      struct utmpx32
+      {
+        short int ut_type;               /* Type of login.  */
+        pid_t ut_pid;                    /* Process ID of login process.  */
+        char ut_line[UT_LINE_SIZE];      /* Devicename.  */
+        char ut_id[UT_ID_SIZE];          /* Inittab ID.  */
+        char ut_user[UT_USER_SIZE];      /* Username.  */
+        char ut_host[UT_HOST_SIZE];      /* Hostname for remote login. */
+        struct __exit_status ut_exit;    /* Exit status of a process marked
+                                            as DEAD_PROCESS.  */
+        /* The fields ut_session and ut_tv must be the same size when compiled
+           32- and 64-bit.  This allows files and shared memory to be shared
+           between 32- and 64-bit applications.  */
+        int ut_session;                  /* Session ID, used for windowing.  */
+        struct
+        {
+          /* Seconds.  Unsigned not signed, as glibc did not exist before 1970,
+             and if the format is still in use after 2038 its timestamps
+             will surely have the sign bit on.  This hack stops working
+             at 2106-02-07 06:28:16 UTC.  */
+          unsigned int tv_sec;
+          int tv_usec;                   /* Microseconds.  */
+        } ut_tv;                         /* Time entry was made.  */
+        int ut_addr_v6[4];               /* Internet address of remote host.  */
+        char ut_reserved[20];            /* Reserved for future use.  */
+      };
+      struct utmpx32 const *ut = (struct utmpx32 const *) entry;
+#  else
+      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
+#  endif
+
+      a = add_utmp (a, options,
+                    UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
+                    ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
+                    ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
+                    ut->ut_pid,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+                    ut->ut_type,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+                    (struct timespec) { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 },
+                    #else
+                    (struct timespec) { .tv_sec = ut->ut_time, .tv_nsec = 0 },
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
+                    ut->ut_session,
+                    #else
+                    0,
+                    #endif
+                    UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut)
+                   );
+    }
+
+  END_UTMP_ENT ();
+
+# else /* old FreeBSD, OpenBSD, HP-UX */
+
+  FILE *f = fopen (file, "re");
+
+  if (! f)
+    return -1;
+
+  for (;;)
+    {
+      struct UTMP_STRUCT_NAME ut;
+
+      if (fread (&ut, sizeof ut, 1, f) == 0)
+        break;
+      a = add_utmp (a, options,
+                    UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
+                    ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
+                    ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
+                    ut.ut_pid,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+                    ut.ut_type,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+                    (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 },
+                    #else
+                    (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 },
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
+                    ut.ut_session,
+                    #else
+                    0,
+                    #endif
+                    UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut)
+                   );
+    }
+
+  int saved_errno = ferror (f) ? errno : 0;
+  if (fclose (f) != 0)
+    saved_errno = errno;
+  if (saved_errno != 0)
+    {
+      free (a.utmp);
+      errno = saved_errno;
+      return -1;
+    }
+
+# endif
+
+  a = finish_utmp (a);
+
+  *n_entries = a.filled;
+  *utmp_buf = a.utmp;
+
+  return 0;
+}
+
 # if READUTMP_USE_SYSTEMD
 /* Use systemd and Linux /proc and kernel APIs.  */
 
 static struct timespec
 get_boot_time_uncached (void)
 {
+  /* Try to find the boot time in the /var/run/utmp file.  */
+  {
+    idx_t n_entries = 0;
+    STRUCT_UTMP *utmp = NULL;
+    read_utmp_from_file (UTMP_FILE, &n_entries, &utmp, READ_UTMP_BOOT_TIME);
+    if (n_entries > 0)
+      {
+        struct timespec result = utmp[0].ut_ts;
+        free (utmp);
+        return result;
+      }
+    free (utmp);
+  }
+
+  /* The following approaches are only usable as fallbacks, because they are
+     all of the form
+       boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
+     and therefore produce wrong values after the date has been bumped in the
+     running system, which happens frequently if the system is running in a
+     virtual machine and this VM has been put into "saved" or "sleep" state
+     and then resumed.  */
+
   /* The clock_gettime facility returns the uptime with a resolution of 1 µsec.
      It is available with glibc >= 2.14.  In glibc < 2.17 it required linking
      with librt.  */
@@ -602,211 +801,19 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
 
 # endif
 
-# if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
-
-#  if !HAVE_UTMPX_H && HAVE_UTMP_H && !HAVE_DECL_GETUTENT
-struct utmp *getutent (void);
-#  endif
-
 int
 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
-#  if READUTMP_USE_SYSTEMD
+# if READUTMP_USE_SYSTEMD
   if (strcmp (file, UTMP_FILE) == 0)
     /* Imitate reading UTMP_FILE, using systemd and Linux APIs.  */
     return read_utmp_from_systemd (n_entries, utmp_buf, options);
-#  endif
-
-  if ((options & READ_UTMP_BOOT_TIME) != 0
-      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
-    {
-      /* No entries can match the given options.  */
-      *n_entries = 0;
-      *utmp_buf = NULL;
-      return 0;
-    }
-
-  /* Ignore the return value for now.
-     Solaris' utmpname returns 1 upon success -- which is contrary
-     to what the GNU libc version does.  In addition, older GNU libc
-     versions are actually void.   */
-  UTMP_NAME_FUNCTION ((char *) file);
-
-  SET_UTMP_ENT ();
-
-  struct utmp_alloc a = {0};
-  void const *entry;
-
-  while ((entry = GET_UTMP_ENT ()) != NULL)
-    {
-#  if __GLIBC__ && _TIME_BITS == 64
-      /* This is a near-copy of glibc's struct utmpx, which stops working
-         after the year 2038.  Unlike the glibc version, struct utmpx32
-         describes the file format even if time_t is 64 bits.  */
-      struct utmpx32
-      {
-        short int ut_type;               /* Type of login.  */
-        pid_t ut_pid;                    /* Process ID of login process.  */
-        char ut_line[UT_LINE_SIZE];      /* Devicename.  */
-        char ut_id[UT_ID_SIZE];          /* Inittab ID.  */
-        char ut_user[UT_USER_SIZE];      /* Username.  */
-        char ut_host[UT_HOST_SIZE];      /* Hostname for remote login. */
-        struct __exit_status ut_exit;    /* Exit status of a process marked
-                                            as DEAD_PROCESS.  */
-        /* The fields ut_session and ut_tv must be the same size when compiled
-           32- and 64-bit.  This allows files and shared memory to be shared
-           between 32- and 64-bit applications.  */
-        int ut_session;                  /* Session ID, used for windowing.  */
-        struct
-        {
-          /* Seconds.  Unsigned not signed, as glibc did not exist before 1970,
-             and if the format is still in use after 2038 its timestamps
-             will surely have the sign bit on.  This hack stops working
-             at 2106-02-07 06:28:16 UTC.  */
-          unsigned int tv_sec;
-          int tv_usec;                   /* Microseconds.  */
-        } ut_tv;                         /* Time entry was made.  */
-        int ut_addr_v6[4];               /* Internet address of remote host.  */
-        char ut_reserved[20];            /* Reserved for future use.  */
-      };
-      struct utmpx32 const *ut = (struct utmpx32 const *) entry;
-#  else
-      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
-#  endif
-
-      a = add_utmp (a, options,
-                    UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
-                    ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
-                    ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
-                    ut->ut_pid,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
-                    ut->ut_type,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
-                    (struct timespec) { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 },
-                    #else
-                    (struct timespec) { .tv_sec = ut->ut_time, .tv_nsec = 0 },
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
-                    ut->ut_session,
-                    #else
-                    0,
-                    #endif
-                    UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut)
-                   );
-    }
-
-  END_UTMP_ENT ();
-
-  a = finish_utmp (a);
-
-  *n_entries = a.filled;
-  *utmp_buf = a.utmp;
-
-  return 0;
-}
-
-# else /* old FreeBSD, OpenBSD, HP-UX */
-
-int
-read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
-           int options)
-{
-  if ((options & READ_UTMP_BOOT_TIME) != 0
-      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
-    {
-      /* No entries can match the given options.  */
-      *n_entries = 0;
-      *utmp_buf = NULL;
-      return 0;
-    }
-
-  FILE *f = fopen (file, "re");
-
-  if (! f)
-    return -1;
-
-  struct utmp_alloc a = {0};
-
-  for (;;)
-    {
-      struct UTMP_STRUCT_NAME ut;
-
-      if (fread (&ut, sizeof ut, 1, f) == 0)
-        break;
-      a = add_utmp (a, options,
-                    UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
-                    ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
-                    ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
-                    ut.ut_pid,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
-                    ut.ut_type,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
-                    (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 },
-                    #else
-                    (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 },
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
-                    ut.ut_session,
-                    #else
-                    0,
-                    #endif
-                    UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut)
-                   );
-    }
-
-  int saved_errno = ferror (f) ? errno : 0;
-  if (fclose (f) != 0)
-    saved_errno = errno;
-  if (saved_errno != 0)
-    {
-      free (a.utmp);
-      errno = saved_errno;
-      return -1;
-    }
-
-  a = finish_utmp (a);
-
-  *n_entries = a.filled;
-  *utmp_buf = a.utmp;
+# endif
 
-  return 0;
+  return read_utmp_from_file (file, n_entries, utmp_buf, options);
 }
 
-# endif
-
 #else /* dummy fallback */
 
 int