I wrote:
> So, all approaches that compute the boot time through a subtraction are
> going to be wrong in these scenarios.
>
> The better approach is really to read the boot time from a time stamp —
> inside a file such as /var/run/utmp or /var/log/wtmp, or attached to
> a file such as
> /var/lib/systemd/random-seed (first file touched during boot)
> /var/log/boot.log (one of the last files touched during boot).
>
> And on Alpine Linux, while /var/run/utmp is empty, its time stamp is
> essentially the boot time.
With the attached patches, the boot time returned by read_utmp() is stable
across 'sudo date MMDDhhmm' invocations.
On Linux distros, I found these files to be touched after boot:
/var/lib/systemd/random-seed (seen on distros with systemd, e.g. CentOS 7,
Fedora 27, Debian 9, Ubuntu 17.10, Arch 19.11,
Manjaro 17)
/var/run/utmp (seen on distros with OpenRC, e.g. Alpine Linux)
/var/lib/random-seed (seen on OpenSUSE 12.1, CentOS 5)
And, while at it, also on OpenBSD. It touches /var/db/host.random and
/var/run/utmp.
Bruno
2023-08-09 Bruno Haible <[email protected]>
readutmp: Return a boot time also on OpenBSD.
* lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
definitions.
* lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
entry by looking at the time stamp of a specific file.
readutmp: Return a boot time also on Alpine Linux.
* lib/readutmp.c: Include stat-time.h.
(SIZEOF): New macro.
(read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
at the time stamp of a specific file.
* modules/readutmp (Depends-on): Add stat-time.
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.
* lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
Implement them.
(read_utmp): If no entries can match the given options, return
immediately.
From 1e4bc08eb8ce3c00f7b8e72dc69e07ccbc63ed20 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 9 Aug 2023 18:49:22 +0200
Subject: [PATCH 1/4] 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.
* lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
Implement them.
(read_utmp): If no entries can match the given options, return
immediately.
---
ChangeLog | 12 ++-
lib/readutmp.c | 245 +++++++++++++++++++++++++++----------------------
lib/readutmp.h | 12 ++-
3 files changed, 157 insertions(+), 112 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 40274c0a08..c2c9613643 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,17 @@
+2023-08-09 Bruno Haible <[email protected]>
+
+ 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.
+ * lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
+ Implement them.
+ (read_utmp): If no entries can match the given options, return
+ immediately.
+
2023-08-08 Paul Eggert <[email protected]>
readutmp: omit pragma
- * lib/readutmp.c: Omit -Sstringop-overread pragma.
+ * lib/readutmp.c: Omit -Wstringop-overread pragma.
It’s no longer needed now that extract_trimmed_name
no longer calls strnlen.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 31db4023a1..a7773c4f36 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -163,6 +163,13 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
&& ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0')
return false;
# endif
+
+ bool boot_time = UT_TYPE_BOOT_TIME (ut);
+ if ((options & READ_UTMP_BOOT_TIME) && !boot_time)
+ return false;
+ if ((options & READ_UTMP_NO_BOOT_TIME) && boot_time)
+ return false;
+
bool user_proc = IS_USER_PROCESS (ut);
if ((options & READ_UTMP_USER_PROCESS) && !user_proc)
return false;
@@ -171,6 +178,7 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
&& 0 < UT_PID (ut)
&& (kill (UT_PID (ut), 0) < 0 && errno == ESRCH))
return false;
+
return true;
}
@@ -441,7 +449,7 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
struct utmp_alloc a = {0};
/* Synthesize a BOOT_TIME entry. */
- if (!(options & READ_UTMP_USER_PROCESS))
+ if (!(options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)))
a = add_utmp (a, options,
"reboot", strlen ("reboot"),
"", 0,
@@ -450,135 +458,138 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
0, BOOT_TIME, get_boot_time (), 0, 0, 0);
/* Synthesize USER_PROCESS entries. */
- char **sessions;
- int num_sessions = sd_get_sessions (&sessions);
- if (num_sessions >= 0)
+ if (!(options & READ_UTMP_BOOT_TIME))
{
- char **session_ptr;
- for (session_ptr = sessions; *session_ptr != NULL; session_ptr++)
+ char **sessions;
+ int num_sessions = sd_get_sessions (&sessions);
+ if (num_sessions >= 0)
{
- char *session = *session_ptr;
+ char **session_ptr;
+ for (session_ptr = sessions; *session_ptr != NULL; session_ptr++)
+ {
+ char *session = *session_ptr;
- uint64_t start_usec;
- if (sd_session_get_start_time (session, &start_usec) < 0)
- start_usec = 0;
- struct timespec start_ts;
- start_ts.tv_sec = start_usec / 1000000;
- start_ts.tv_nsec = start_usec % 1000000 * 1000;
+ uint64_t start_usec;
+ if (sd_session_get_start_time (session, &start_usec) < 0)
+ start_usec = 0;
+ struct timespec start_ts;
+ start_ts.tv_sec = start_usec / 1000000;
+ start_ts.tv_nsec = start_usec % 1000000 * 1000;
- char *seat;
- if (sd_session_get_seat (session, &seat) < 0)
- seat = NULL;
+ char *seat;
+ if (sd_session_get_seat (session, &seat) < 0)
+ seat = NULL;
- char missing[] = "";
+ char missing[] = "";
- char *type = NULL;
- char *tty;
- if (sd_session_get_tty (session, &tty) < 0)
- {
- tty = NULL;
- /* Try harder to get a sensible value for the tty. */
- if (sd_session_get_type (session, &type) < 0)
- type = missing;
- if (strcmp (type, "tty") == 0)
+ char *type = NULL;
+ char *tty;
+ if (sd_session_get_tty (session, &tty) < 0)
{
- char *service;
- if (sd_session_get_service (session, &service) < 0)
- service = NULL;
-
- uid_t uid;
- char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
- : guess_pty_name (uid, start_ts));
-
- if (service != NULL && pty != NULL)
+ tty = NULL;
+ /* Try harder to get a sensible value for the tty. */
+ if (sd_session_get_type (session, &type) < 0)
+ type = missing;
+ if (strcmp (type, "tty") == 0)
{
- tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
- stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
- free (pty);
- free (service);
+ char *service;
+ if (sd_session_get_service (session, &service) < 0)
+ service = NULL;
+
+ uid_t uid;
+ char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
+ : guess_pty_name (uid, start_ts));
+
+ if (service != NULL && pty != NULL)
+ {
+ tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
+ stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
+ free (pty);
+ free (service);
+ }
+ else if (service != NULL)
+ tty = service;
+ else if (pty != NULL)
+ tty = pty;
}
- else if (service != NULL)
- tty = service;
- else if (pty != NULL)
- tty = pty;
}
- }
- /* Create up to two USER_PROCESS entries: one for the seat,
- one for the tty. */
- if (seat != NULL || tty != NULL)
- {
- char *user;
- if (sd_session_get_username (session, &user) < 0)
- user = missing;
+ /* Create up to two USER_PROCESS entries: one for the seat,
+ one for the tty. */
+ if (seat != NULL || tty != NULL)
+ {
+ char *user;
+ if (sd_session_get_username (session, &user) < 0)
+ user = missing;
- pid_t leader_pid;
- if (sd_session_get_leader (session, &leader_pid) < 0)
- leader_pid = 0;
+ pid_t leader_pid;
+ if (sd_session_get_leader (session, &leader_pid) < 0)
+ leader_pid = 0;
- char *host;
- char *remote_host;
- if (sd_session_get_remote_host (session, &remote_host) < 0)
- {
- host = missing;
- /* For backward compatibility, put the X11 display into the
- host field. */
- if (!type && sd_session_get_type (session, &type) < 0)
- type = missing;
- if (strcmp (type, "x11") == 0)
+ char *host;
+ char *remote_host;
+ if (sd_session_get_remote_host (session, &remote_host) < 0)
{
- char *display;
- if (sd_session_get_display (session, &display) < 0)
- display = NULL;
- host = display;
+ host = missing;
+ /* For backward compatibility, put the X11 display into the
+ host field. */
+ if (!type && sd_session_get_type (session, &type) < 0)
+ type = missing;
+ if (strcmp (type, "x11") == 0)
+ {
+ char *display;
+ if (sd_session_get_display (session, &display) < 0)
+ display = NULL;
+ host = display;
+ }
}
- }
- else
- {
- char *remote_user;
- if (sd_session_get_remote_user (session, &remote_user) < 0)
- host = remote_host;
else
{
- host = xmalloc (strlen (remote_user) + 1
- + strlen (remote_host) + 1);
- stpcpy (stpcpy (stpcpy (host, remote_user), "@"),
- remote_host);
- free (remote_user);
- free (remote_host);
+ char *remote_user;
+ if (sd_session_get_remote_user (session, &remote_user) < 0)
+ host = remote_host;
+ else
+ {
+ host = xmalloc (strlen (remote_user) + 1
+ + strlen (remote_host) + 1);
+ stpcpy (stpcpy (stpcpy (host, remote_user), "@"),
+ remote_host);
+ free (remote_user);
+ free (remote_host);
+ }
}
+
+ if (seat != NULL)
+ a = add_utmp (a, options,
+ user, strlen (user),
+ session, strlen (session),
+ seat, strlen (seat),
+ host, strlen (host),
+ leader_pid /* the best we have */,
+ USER_PROCESS, start_ts, leader_pid, 0, 0);
+ if (tty != NULL)
+ a = add_utmp (a, options,
+ user, strlen (user),
+ session, strlen (session),
+ tty, strlen (tty),
+ host, strlen (host),
+ leader_pid /* the best we have */,
+ USER_PROCESS, start_ts, leader_pid, 0, 0);
+
+ if (host != missing)
+ free (host);
+ if (user != missing)
+ free (user);
}
- if (seat != NULL)
- a = add_utmp (a, options,
- user, strlen (user),
- session, strlen (session),
- seat, strlen (seat),
- host, strlen (host),
- leader_pid /* the best we have */,
- USER_PROCESS, start_ts, leader_pid, 0, 0);
- if (tty != NULL)
- a = add_utmp (a, options,
- user, strlen (user),
- session, strlen (session),
- tty, strlen (tty),
- host, strlen (host),
- leader_pid /* the best we have */,
- USER_PROCESS, start_ts, leader_pid, 0, 0);
-
- if (host != missing)
- free (host);
- if (user != missing)
- free (user);
+ if (type != missing)
+ free (type);
+ free (tty);
+ free (seat);
+ free (session);
}
-
- if (type != missing)
- free (type);
- free (tty);
- free (seat);
- free (session);
+ free (sessions);
}
- free (sessions);
}
a = finish_utmp (a);
@@ -607,6 +618,15 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
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
@@ -708,6 +728,15 @@ 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)
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 9377a1b57f..88c0d94303 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -69,7 +69,7 @@ struct gl_utmp
struct timespec ut_ts; /* time */
pid_t ut_pid; /* process ID of ? */
pid_t ut_session; /* process ID of session leader */
- short ut_type; /* BOOT_TIME or USER_PROCESS */
+ short ut_type; /* BOOT_TIME, USER_PROCESS, or other */
struct { int e_termination; int e_exit; } ut_exit;
};
@@ -237,8 +237,10 @@ enum { UT_HOST_SIZE = -1 };
/* Options for read_utmp. */
enum
{
- READ_UTMP_CHECK_PIDS = 1,
- READ_UTMP_USER_PROCESS = 2
+ READ_UTMP_CHECK_PIDS = 1,
+ READ_UTMP_USER_PROCESS = 2,
+ READ_UTMP_BOOT_TIME = 4,
+ READ_UTMP_NO_BOOT_TIME = 8
};
/* Return a copy of (UT)->ut_user, without trailing spaces,
@@ -256,6 +258,10 @@ char *extract_trimmed_name (const STRUCT_UTMP *ut)
process-IDs do not currently exist.
If OPTIONS & READ_UTMP_USER_PROCESS is nonzero, omit entries which
do not correspond to a user process.
+ If OPTIONS & READ_UTMP_BOOT_TIME is nonzero, omit all entries except
+ the one that contains the boot time.
+ If OPTIONS & READ_UTMP_NO_BOOT_TIME is nonzero, omit the boot time
+ entries.
This function is not multithread-safe, since on many platforms it
invokes the functions setutxent, getutxent, endutxent. These
--
2.34.1
From 321daa595e02717c7dca111184fc091fda3862f8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 9 Aug 2023 21:12:40 +0200
Subject: [PATCH 2/4] 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.
---
ChangeLog | 7 +
lib/readutmp.c | 397 +++++++++++++++++++++++++------------------------
2 files changed, 209 insertions(+), 195 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index c2c9613643..3725c3bf9e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
2023-08-09 Bruno Haible <[email protected]>
+ 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.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index a7773c4f36..f7a839a1eb 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -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
--
2.34.1
>From 51f5c00413f3b2fc42520d4f520a8b9181afe531 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 9 Aug 2023 22:27:16 +0200
Subject: [PATCH 3/4] readutmp: Return a boot time also on Alpine Linux.
* lib/readutmp.c: Include stat-time.h.
(SIZEOF): New macro.
(read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
at the time stamp of a specific file.
* modules/readutmp (Depends-on): Add stat-time.
---
ChangeLog | 7 +++++++
lib/readutmp.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
modules/readutmp | 1 +
3 files changed, 56 insertions(+)
diff --git a/ChangeLog b/ChangeLog
index 3725c3bf9e..c6e7001859 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
2023-08-09 Bruno Haible <[email protected]>
+ readutmp: Return a boot time also on Alpine Linux.
+ * lib/readutmp.c: Include stat-time.h.
+ (SIZEOF): New macro.
+ (read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
+ at the time stamp of a specific file.
+ * modules/readutmp (Depends-on): Add stat-time.
+
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.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index f7a839a1eb..2eb3395e0c 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -38,6 +38,7 @@
# include <time.h>
#endif
+#include "stat-time.h"
#include "xalloc.h"
/* Each of the FILE streams in this file is only used in a single thread. */
@@ -132,6 +133,8 @@
/* Size of the ut->ut_host member. */
#define UT_HOST_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host)
+#define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
+
#if 8 <= __GNUC__
# pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
#endif
@@ -388,6 +391,51 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
END_UTMP_ENT ();
+# if defined __linux__
+ /* On Alpine Linux, UTMP_FILE is not filled. It is always empty.
+ So, fake a BOOT_TIME entry, by getting the time stamp of a file that
+ gets touched only during the boot process. */
+ if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
+ && strcmp (file, UTMP_FILE) == 0)
+ {
+ bool have_boot_time = false;
+ for (idx_t i = 0; i < a.filled; i++)
+ {
+ struct gl_utmp *ut = &a.utmp[i];
+ if (UT_TYPE_BOOT_TIME (ut))
+ {
+ have_boot_time = true;
+ break;
+ }
+ }
+ if (!have_boot_time)
+ {
+ const char * const boot_touched_files[] =
+ {
+ "/var/lib/systemd/random-seed", /* seen on distros with systemd */
+ "/var/run/utmp", /* seen on distros with OpenRC */
+ "/var/lib/random-seed" /* seen on older distros */
+ };
+ for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+ {
+ const char *filename = boot_touched_files[i];
+ struct stat statbuf;
+ if (stat (filename, &statbuf) >= 0)
+ {
+ struct timespec boot_time = get_stat_mtime (&statbuf);
+ a = add_utmp (a, options,
+ "reboot", strlen ("reboot"),
+ "", 0,
+ "~", strlen ("~"),
+ "", 0,
+ 0, BOOT_TIME, boot_time, 0, 0, 0);
+ break;
+ }
+ }
+ }
+ }
+# endif
+
# else /* old FreeBSD, OpenBSD, HP-UX */
FILE *f = fopen (file, "re");
diff --git a/modules/readutmp b/modules/readutmp
index 15c63a3d5e..21f6de5777 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -11,6 +11,7 @@ Depends-on:
extensions
fopen-gnu
idx
+stat-time
stdbool
stdint
strnlen
--
2.34.1
>From e069091227c58e0bb4f8bfb091f102f55f181f23 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 9 Aug 2023 22:52:22 +0200
Subject: [PATCH 4/4] readutmp: Return a boot time also on OpenBSD.
* lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
definitions.
* lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
entry by looking at the time stamp of a specific file.
---
ChangeLog | 6 ++++++
lib/readutmp.c | 36 ++++++++++++++++++++++++++++++++++++
lib/readutmp.h | 8 ++++++++
3 files changed, 50 insertions(+)
diff --git a/ChangeLog b/ChangeLog
index c6e7001859..6fde91a679 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
2023-08-09 Bruno Haible <[email protected]>
+ readutmp: Return a boot time also on OpenBSD.
+ * lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
+ definitions.
+ * lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
+ entry by looking at the time stamp of a specific file.
+
readutmp: Return a boot time also on Alpine Linux.
* lib/readutmp.c: Include stat-time.h.
(SIZEOF): New macro.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 2eb3395e0c..537846c144 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -496,6 +496,42 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
return -1;
}
+# if defined __OpenBSD__
+ /* On OpenBSD, UTMP_FILE is not filled. It contains only dummy entries.
+ So, fake a BOOT_TIME entry, by getting the time stamp of a file that
+ gets touched only during the boot process. */
+ if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
+ && strcmp (file, UTMP_FILE) == 0)
+ {
+ /* OpenBSD's 'struct utmp' does not have an ut_type field. */
+ bool have_boot_time = false;
+ if (!have_boot_time)
+ {
+ const char * const boot_touched_files[] =
+ {
+ "/var/db/host.random",
+ "/var/run/utmp"
+ };
+ for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+ {
+ const char *filename = boot_touched_files[i];
+ struct stat statbuf;
+ if (stat (filename, &statbuf) >= 0)
+ {
+ struct timespec boot_time = get_stat_mtime (&statbuf);
+ a = add_utmp (a, options,
+ "reboot", strlen ("reboot"),
+ "", 0,
+ "", strlen (""),
+ "", 0,
+ 0, BOOT_TIME, boot_time, 0, 0, 0);
+ break;
+ }
+ }
+ }
+ }
+# endif
+
# endif
a = finish_utmp (a);
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 88c0d94303..b74d37cde3 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -211,6 +211,14 @@ enum { UT_HOST_SIZE = -1 };
# define WTMP_FILE "/etc/wtmp"
#endif
+/* Some platforms, such as OpenBSD, don't have an ut_type field and don't have
+ the BOOT_TIME and USER_PROCESS macros. But we want to support them in
+ 'struct gl_utmp'. */
+#if !(HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+# define BOOT_TIME 2
+# define USER_PROCESS 0
+#endif
+
/* Macros that test (UT)->ut_type. */
#ifdef BOOT_TIME
# define UT_TYPE_BOOT_TIME(UT) UT_TYPE_EQ (UT, BOOT_TIME)
--
2.34.1