mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-24 07:20:37 +00:00
295 lines
7.8 KiB
C
295 lines
7.8 KiB
C
/* Determine the time when the machine last booted.
|
|
Copyright (C) 2023-2024 Free Software Foundation, Inc.
|
|
|
|
This file is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published
|
|
by the Free Software Foundation, either version 3 of the License,
|
|
or (at your option) any later version.
|
|
|
|
This file is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by Bruno Haible <bruno@clisp.org>. */
|
|
|
|
#include <config.h>
|
|
|
|
/* Specification. */
|
|
#include "boot-time.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined __linux__ || defined __ANDROID__
|
|
# include <sys/sysinfo.h>
|
|
# include <time.h>
|
|
#endif
|
|
|
|
#if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined __minix
|
|
# if HAVE_SYS_PARAM_H
|
|
# include <sys/param.h>
|
|
# endif
|
|
# include <sys/sysctl.h>
|
|
#endif
|
|
|
|
#if HAVE_OS_H
|
|
# include <OS.h>
|
|
#endif
|
|
|
|
#include "idx.h"
|
|
#include "readutmp.h"
|
|
#include "stat-time.h"
|
|
|
|
/* Each of the FILE streams in this file is only used in a single thread. */
|
|
#include "unlocked-io.h"
|
|
|
|
/* Some helper functions. */
|
|
#include "boot-time-aux.h"
|
|
|
|
/* The following macros describe the 'struct UTMP_STRUCT_NAME',
|
|
*not* 'struct gl_utmp'. */
|
|
#undef UT_USER
|
|
|
|
/* Accessor macro for the member named ut_user or ut_name. */
|
|
#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \
|
|
: HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)
|
|
# define UT_USER(UT) ((UT)->ut_name)
|
|
#else
|
|
# define UT_USER(UT) ((UT)->ut_user)
|
|
#endif
|
|
|
|
#if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION
|
|
# if !HAVE_DECL_ENDUTENT /* Android */
|
|
void endutent (void);
|
|
# endif
|
|
#endif
|
|
|
|
#if defined __linux__ || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ || defined _WIN32
|
|
|
|
static int
|
|
get_boot_time_uncached (struct timespec *p_boot_time)
|
|
{
|
|
struct timespec found_boot_time = {0};
|
|
|
|
# if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
|
|
|
|
/* Try to find the boot time in the /var/run/utmp file. */
|
|
|
|
# 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 *) UTMP_FILE);
|
|
|
|
SET_UTMP_ENT ();
|
|
|
|
# if (defined __linux__ && !defined __ANDROID__) || defined __minix
|
|
/* Timestamp of the "runlevel" entry, if any. */
|
|
struct timespec runlevel_ts = {0};
|
|
# endif
|
|
|
|
void const *entry;
|
|
|
|
while ((entry = GET_UTMP_ENT ()) != NULL)
|
|
{
|
|
struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
|
|
|
|
struct timespec ts =
|
|
#if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
|
|
{ .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 };
|
|
#else
|
|
{ .tv_sec = ut->ut_time, .tv_nsec = 0 };
|
|
#endif
|
|
|
|
if (ut->ut_type == BOOT_TIME)
|
|
found_boot_time = ts;
|
|
|
|
# if defined __linux__ && !defined __ANDROID__
|
|
if (memcmp (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) == 0
|
|
&& memcmp (ut->ut_line, "~", strlen ("~") + 1) == 0)
|
|
runlevel_ts = ts;
|
|
# endif
|
|
# if defined __minix
|
|
if (UT_USER (ut)[0] == '\0'
|
|
&& memcmp (ut->ut_line, "run-level ", strlen ("run-level ")) == 0)
|
|
runlevel_ts = ts;
|
|
# endif
|
|
}
|
|
|
|
END_UTMP_ENT ();
|
|
|
|
# if defined __linux__ && !defined __ANDROID__
|
|
/* On Raspbian, which runs on hardware without a real-time clock, during boot,
|
|
1. the clock gets set to 1970-01-01 00:00:00,
|
|
2. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
|
|
ut_user = "reboot", ut_line = "~", time = 1970-01-01 00:00:05 or so,
|
|
3. the clock gets set to a correct value through NTP,
|
|
4. an entry gets written into /var/run/utmp, with
|
|
ut_user = "runlevel", ut_line = "~", time = correct value.
|
|
In this case, get the time from the "runlevel" entry. */
|
|
|
|
/* Workaround for Raspbian: */
|
|
if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
|
|
found_boot_time = runlevel_ts;
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
/* Workaround for Alpine Linux: */
|
|
get_linux_boot_time_fallback (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# if defined __ANDROID__
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
/* Workaround for Android: */
|
|
get_android_boot_time (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# if defined __minix
|
|
/* On Minix, during boot,
|
|
1. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
|
|
ut_user = "", ut_line = "system boot", time = 1970-01-01 00:00:00,
|
|
2. an entry gets written into /var/run/utmp, with
|
|
ut_user = "", ut_line = "run-level m", time = correct value.
|
|
In this case, copy the time from the "run-level m" entry to the
|
|
"system boot" entry. */
|
|
if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
|
|
found_boot_time = runlevel_ts;
|
|
# endif
|
|
|
|
# else /* HP-UX, Haiku */
|
|
|
|
FILE *f = fopen (UTMP_FILE, "re");
|
|
|
|
if (f != NULL)
|
|
{
|
|
for (;;)
|
|
{
|
|
struct UTMP_STRUCT_NAME ut;
|
|
|
|
if (fread (&ut, sizeof ut, 1, f) == 0)
|
|
break;
|
|
|
|
struct timespec ts =
|
|
#if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
|
|
{ .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 };
|
|
#else
|
|
{ .tv_sec = ut.ut_time, .tv_nsec = 0 };
|
|
#endif
|
|
|
|
if (ut.ut_type == BOOT_TIME)
|
|
found_boot_time = ts;
|
|
}
|
|
|
|
fclose (f);
|
|
}
|
|
|
|
# endif
|
|
|
|
# if defined __linux__ && !defined __ANDROID__
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
get_linux_boot_time_final_fallback (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# else /* Adélie Linux, old FreeBSD, OpenBSD, native Windows */
|
|
|
|
# if defined __linux__ && !defined __ANDROID__
|
|
/* Workaround for Adélie Linux: */
|
|
get_linux_boot_time_fallback (&found_boot_time);
|
|
if (found_boot_time.tv_sec == 0)
|
|
get_linux_boot_time_final_fallback (&found_boot_time);
|
|
# endif
|
|
|
|
# if defined __OpenBSD__
|
|
/* Workaround for OpenBSD: */
|
|
get_openbsd_boot_time (&found_boot_time);
|
|
# endif
|
|
|
|
# endif
|
|
|
|
# if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
|
|
&& defined CTL_KERN && defined KERN_BOOTTIME \
|
|
&& !defined __minix
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
get_bsd_boot_time_final_fallback (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# if defined __HAIKU__
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
get_haiku_boot_time (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# if HAVE_OS_H
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
get_haiku_boot_time_final_fallback (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
# if defined __CYGWIN__ || defined _WIN32
|
|
if (found_boot_time.tv_sec == 0)
|
|
{
|
|
/* Workaround for Windows: */
|
|
get_windows_boot_time (&found_boot_time);
|
|
}
|
|
# endif
|
|
|
|
if (found_boot_time.tv_sec != 0)
|
|
{
|
|
*p_boot_time = found_boot_time;
|
|
return 0;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
get_boot_time (struct timespec *p_boot_time)
|
|
{
|
|
/* Cache the result from get_boot_time_uncached. */
|
|
static int volatile cached_result = -1;
|
|
static struct timespec volatile cached_boot_time;
|
|
|
|
if (cached_result < 0)
|
|
{
|
|
struct timespec boot_time;
|
|
int result = get_boot_time_uncached (&boot_time);
|
|
cached_boot_time = boot_time;
|
|
cached_result = result;
|
|
}
|
|
|
|
if (cached_result == 0)
|
|
{
|
|
*p_boot_time = cached_boot_time;
|
|
return 0;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
get_boot_time (struct timespec *p_boot_time)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
#endif
|