diff --git a/0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch b/0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch new file mode 100644 index 0000000..cdc66cf --- /dev/null +++ b/0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch @@ -0,0 +1,159 @@ +From 8cf40027b920b3760d6d1df9eb280b4f3772f290 Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Thu, 3 Oct 2024 21:37:58 +0000 +Subject: [PATCH] needs-restarting: Get boot time from systemd + UnitsLoadStartTimestamp + +Resolves https://issues.redhat.com/browse/RHEL-35577. + +Get the boot time from UnitsLoadStartTimestamp if systemd is available, +but make sure to use the kernel boot time for calculating process start +times using data from procfs. The previous attempt [1] at this failed to +do so and introduced a regression [2]. + +Also, get the kernel boot time from the btime field of /proc/stat instead of +calculating it from /proc/uptime, to be consistent with what procps-ng +does. + +[1] https://github.com/rpm-software-management/dnf-plugins-core/pull/527 +[2] https://issues.redhat.com/browse/RHEL-39775 +--- + plugins/needs_restarting.py | 106 +++++++++++++++++++++++++++--------- + 1 file changed, 81 insertions(+), 25 deletions(-) + +diff --git a/plugins/needs_restarting.py b/plugins/needs_restarting.py +index 309a216..9c77af7 100644 +--- a/plugins/needs_restarting.py ++++ b/plugins/needs_restarting.py +@@ -203,36 +203,89 @@ class OpenedFile(object): + return match.group(1) + return self.name + +- + class ProcessStart(object): + def __init__(self): +- self.boot_time = self.get_boot_time() +- self.sc_clk_tck = self.get_sc_clk_tck() ++ self.kernel_boot_time = ProcessStart.get_kernel_boot_time() ++ self.boot_time = ProcessStart.get_boot_time(self.kernel_boot_time) ++ self.sc_clk_tck = ProcessStart.get_sc_clk_tck() ++ ++ @staticmethod ++ def get_kernel_boot_time(): ++ try: ++ with open('/proc/stat', 'r') as file: ++ for line in file: ++ if line.startswith("btime "): ++ key, value = line.split() ++ return float(value) ++ except OSError as e: ++ logger.debug("Couldn't read /proc/stat: %s", e) ++ return 0 + + @staticmethod +- def get_boot_time(): ++ def get_boot_time(kernel_boot_time): + """ +- We have two sources from which to derive the boot time. These values vary ++ We have three sources from which to derive the boot time. These values vary + depending on containerization, existence of a Real Time Clock, etc. +- For our purposes we want the latest derived value. ++ - UnitsLoadStartTimestamp property on /org/freedesktop/systemd1 ++ The start time of the service manager, according to systemd itself. ++ Seems to be more reliable than UserspaceTimestamp when the RTC is ++ in local time. Works unless the system was not booted with systemd, ++ such as in (most) containers. + - st_mtime of /proc/1 +- Reflects the time the first process was run after booting +- This works for all known cases except machines without +- a RTC - they awake at the start of the epoch. +- - /proc/uptime +- Seconds field of /proc/uptime subtracted from the current time +- Works for machines without RTC iff the current time is reasonably correct. +- Does not work on containers which share their kernel with the +- host - there the host kernel uptime is returned ++ Reflects the time the first process was run after booting. This ++ works for all known cases except machines without a RTC---they ++ awake at the start of the epoch. ++ - btime field of /proc/stat ++ Reflects the time when the kernel started. Works for machines ++ without RTC iff the current time is reasonably correct. Does not ++ work on containers which share their kernel with the host---there, ++ the host kernel uptime is returned. + """ + +- proc_1_boot_time = int(os.stat('/proc/1').st_mtime) +- if os.path.isfile('/proc/uptime'): +- with open('/proc/uptime', 'rb') as f: +- uptime = f.readline().strip().split()[0].strip() +- proc_uptime_boot_time = int(time.time() - float(uptime)) +- return max(proc_1_boot_time, proc_uptime_boot_time) +- return proc_1_boot_time ++ units_load_start_timestamp = None ++ try: ++ # systemd timestamps are the preferred method to determine boot ++ # time. max(proc_1_boot_time, kernel_boot_time) does not allow us ++ # to disambiguate between an unreliable RTC (e.g. the RTC is in ++ # UTC+1 instead of UTC) and a container with a proc_1_boot_time > ++ # kernel_boot_time. So we use UnitsLoadStartTimestamp if it's ++ # available, else fall back to the other methods. ++ bus = dbus.SystemBus() ++ systemd1 = bus.get_object( ++ 'org.freedesktop.systemd1', ++ '/org/freedesktop/systemd1' ++ ) ++ props = dbus.Interface( ++ systemd1, ++ dbus.PROPERTIES_IFACE ++ ) ++ units_load_start_timestamp = props.Get( ++ 'org.freedesktop.systemd1.Manager', ++ 'UnitsLoadStartTimestamp' ++ ) ++ if units_load_start_timestamp != 0: ++ systemd_boot_time = units_load_start_timestamp / (1000 * 1000) ++ logger.debug("Got boot time from systemd: %s", systemd_boot_time) ++ return systemd_boot_time ++ except dbus.exceptions.DBusException as e: ++ logger.debug("D-Bus error getting boot time from systemd: %s", e) ++ ++ logger.debug("Couldn't get boot time from systemd, checking st_mtime of /proc/1 and btime field of /proc/stat.") ++ ++ try: ++ proc_1_boot_time = float(os.stat('/proc/1').st_mtime) ++ except OSError as e: ++ logger.debug("Couldn't stat /proc/1: %s", e) ++ proc_1_boot_time = 1 ++ kernel_boot_time = kernel_boot_time ++ ++ boot_time = max(proc_1_boot_time, kernel_boot_time) ++ ++ logger.debug("st_mtime of /proc/1: %s", proc_1_boot_time) ++ logger.debug("btime field of /proc/stat: %s", kernel_boot_time) ++ logger.debug("Using %s as the system boot time.", boot_time) ++ ++ return boot_time + + @staticmethod + def get_sc_clk_tck(): +@@ -241,10 +294,13 @@ class ProcessStart(object): + def __call__(self, pid): + stat_fn = '/proc/%d/stat' % pid + with open(stat_fn) as stat_file: +- stats = stat_file.read().strip().split() +- ticks_after_boot = int(stats[21]) +- secs_after_boot = ticks_after_boot // self.sc_clk_tck +- return self.boot_time + secs_after_boot ++ stats = stat_file.read().split() ++ ticks_after_kernel_boot = int(stats[21]) ++ secs_after_kernel_boot = ticks_after_kernel_boot / self.sc_clk_tck ++ ++ # The process's start time is always measured relative to the kernel ++ # start time, not either of the other methods we use to get "boot time". ++ return self.kernel_boot_time + secs_after_kernel_boot + + + @dnf.plugin.register_command +-- +2.47.0 + diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec index aee16eb..330d8bb 100644 --- a/dnf-plugins-core.spec +++ b/dnf-plugins-core.spec @@ -34,7 +34,7 @@ Name: dnf-plugins-core Version: 4.3.0 -Release: 17%{?dist} +Release: 18%{?dist} Summary: Core Plugins for DNF License: GPLv2+ URL: https://github.com/rpm-software-management/dnf-plugins-core @@ -55,6 +55,7 @@ Patch13: 0013-Fix-for-issue-with-binary-garbage-in-smaps-files.patch Patch14: 0014-needs-restarting-Add-microcode_ctl-to-a-reboot-list.patch Patch18: 0018-system-upgrade-change-http-to-https-in-unit-file.patch Patch19: 0019-reposync-Respect-norepopath-with-metadata-path.patch +Patch20: 0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch BuildArch: noarch BuildRequires: cmake @@ -802,6 +803,9 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/ %endif %changelog +* Thu Nov 21 2024 Evan Goode - 4.3.0-18 +- needs-restarting: Get boot time from systemd UnitsLoadStartTimestamp (RHEL-14900) + * Thu Oct 10 2024 Pavla Kratochvilova - 4.3.0-17 - reposync: Respect --norepopath with --metadata-path (RHEL-40914)