Skip to content

Commit 486855d

Browse files
committed
needs-restarting: Don't suggest restarting NEED_REBOOT services (RHEL-98293)
"needs-restarting -s" used to list dbus-broker.service for restart after package updates like glibc. Restarting dbus-broker breaks system logins, firewalld, and other D-Bus clients. Two layers of filtering are added to the -s output: 1. Stale files from NEED_REBOOT packages updated since boot are skipped. This reuses the existing -r (reboothint) check. 2. Services whose binary belongs to a NEED_REBOOT package are excluded. This covers the case where a service like dbus-broker is stale due to a non-NEED_REBOOT dependency (e.g. libselinux). The -r mode now also detects services that cannot be safely restarted and includes them in the reboot recommendation. Default mode (no flags) is unchanged — all stale PIDs are reported. Signed-off-by: Marek Blaha <mblaha@redhat.com>
1 parent fa966d8 commit 486855d

2 files changed

Lines changed: 113 additions & 70 deletions

File tree

doc/needs_restarting.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ All general DNF options are accepted, see `Options` in :manpage:`dnf(8)` for det
7272

7373
``-r, --reboothint``
7474
Only report whether a reboot is required (exit code 1) or not (exit code 0).
75+
This checks for updated packages that require a reboot and for services
76+
that require a full system reboot instead of ``systemctl`` restart.
7577

7678
``-s, --services``
77-
Only list the affected systemd services.
79+
Only list the affected systemd services that can be safely restarted
80+
via ``systemctl``. Services that require a full system reboot (such as
81+
``dbus-broker``) are excluded — use ``-r`` to check whether a reboot
82+
is required.
7883

7984
``--exclude-services``
8085
Don't list stale processes that correspond to a systemd service.

plugins/needs_restarting.py

Lines changed: 107 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -130,41 +130,41 @@ def print_cmd(pid):
130130

131131

132132
def get_service_dbus(pid):
133-
bus = dbus.SystemBus()
134-
systemd_manager_object = bus.get_object(
135-
'org.freedesktop.systemd1',
136-
'/org/freedesktop/systemd1'
137-
)
138-
systemd_manager_interface = dbus.Interface(
139-
systemd_manager_object,
140-
'org.freedesktop.systemd1.Manager'
141-
)
142-
143-
service_unit_path = None
144133
try:
145-
service_unit_path = systemd_manager_interface.GetUnitByPID(pid)
134+
bus = dbus.SystemBus()
135+
systemd_manager_object = bus.get_object(
136+
'org.freedesktop.systemd1',
137+
'/org/freedesktop/systemd1'
138+
)
139+
systemd_manager_interface = dbus.Interface(
140+
systemd_manager_object,
141+
'org.freedesktop.systemd1.Manager'
142+
)
143+
144+
service_unit_path = None
145+
try:
146+
service_unit_path = systemd_manager_interface.GetUnitByPID(pid)
147+
except dbus.DBusException as e:
148+
msg = str(e)
149+
if msg.startswith('org.freedesktop.systemd1.NoUnitForPID'):
150+
logger.warning("Failed to get systemd unit for PID {}: {}".format(pid, msg))
151+
return
152+
else:
153+
raise
154+
155+
service_proxy = bus.get_object('org.freedesktop.systemd1', service_unit_path)
156+
service_properties = dbus.Interface(
157+
service_proxy, dbus_interface="org.freedesktop.DBus.Properties")
158+
name = service_properties.Get(
159+
"org.freedesktop.systemd1.Unit",
160+
'Id'
161+
)
162+
if name.endswith(".service"):
163+
return name
164+
return
146165
except dbus.DBusException as e:
147-
# There is no unit for the pid. Usually error is 'NoUnitForPid'.
148-
# Considering what we do at the bottom (just return if not service)
149-
# Then there's really no reason to exit here on that exception.
150-
# Log what's happened then move on.
151-
msg = str(e)
152-
if msg.startswith('org.freedesktop.systemd1.NoUnitForPID'):
153-
logger.warning("Failed to get systemd unit for PID {}: {}".format(pid, msg))
154-
return
155-
else:
156-
raise
157-
158-
service_proxy = bus.get_object('org.freedesktop.systemd1', service_unit_path)
159-
service_properties = dbus.Interface(
160-
service_proxy, dbus_interface="org.freedesktop.DBus.Properties")
161-
name = service_properties.Get(
162-
"org.freedesktop.systemd1.Unit",
163-
'Id'
164-
)
165-
if name.endswith(".service"):
166-
return name
167-
return
166+
logger.warning("Failed to query D-Bus for PID {}: {}".format(pid, e))
167+
return
168168

169169
def smap2opened_file(pid, line):
170170
slash = line.find('/')
@@ -335,30 +335,16 @@ def run(self):
335335
"etc/dnf/plugins/needs-restarting.d/"),
336336
self.base)
337337
NEED_REBOOT.extend(opt)
338-
if self.opts.reboothint:
339-
need_reboot = set()
340-
installed = self.base.sack.query().installed()
341-
for pkg in installed.filter(provides=NEED_REBOOT):
342-
if pkg.installtime > process_start.boot_time:
343-
need_reboot.add(pkg.name)
344-
if need_reboot:
345-
print(_('Core libraries or services have been updated '
346-
'since boot-up:'))
347-
for name in sorted(need_reboot):
348-
print(' * %s' % name)
349-
print()
350-
print(_('Reboot is required to fully utilize these updates.'))
351-
print(_('More information:'),
352-
'https://access.redhat.com/solutions/27943')
353-
raise dnf.exceptions.Error() # Sets exit code 1
354-
else:
355-
print(_('No core libraries or services have been updated '
356-
'since boot-up.'))
357-
print(_('Reboot should not be necessary.'))
358-
return None
338+
339+
need_reboot = set()
340+
installed_need_reboot_pkgs = set()
341+
for pkg in self.base.sack.query().installed().filterm(provides=NEED_REBOOT):
342+
installed_need_reboot_pkgs.add(pkg.name)
343+
if pkg.installtime > process_start.boot_time:
344+
need_reboot.add(pkg.name)
359345

360346
stale_pids = set()
361-
stale_service_names = set()
347+
stale_service_pids = {}
362348
uid = os.geteuid() if self.opts.useronly else None
363349
for ofile in list_opened_files(uid):
364350
pkg = owning_pkg_fn(ofile.presumed_name)
@@ -367,23 +353,75 @@ def run(self):
367353
continue
368354
if pkg.installtime <= process_start(pid):
369355
continue
370-
if self.opts.services or self.opts.exclude_services:
356+
if self.opts.services or self.opts.exclude_services \
357+
or self.opts.reboothint:
358+
# All three modes need service detection:
359+
# -s: to list services that need restart
360+
# --exclude-services: to filter services from PID output
361+
# -r: to identify services with NEED_REBOOT binaries (checked later)
362+
363+
# For -s and -r: skip stale files from NEED_REBOOT packages.
364+
# Those services require a reboot, not a restart.
365+
if (self.opts.services or self.opts.reboothint) \
366+
and pkg.name in need_reboot:
367+
continue
371368
service_name = get_service_dbus(pid)
372369
if service_name is None:
373-
stale_pids.add(pid)
374-
else:
375-
stale_service_names.add(service_name)
376-
if not self.opts.exclude_services:
370+
if self.opts.exclude_services:
377371
stale_pids.add(pid)
372+
else:
373+
stale_service_pids.setdefault(service_name, []).append(pid)
378374
else:
379-
# If neither --services nor --exclude-services is set, don't
380-
# query D-Bus at all.
375+
# Default mode: collect all stale PIDs without service filtering
381376
stale_pids.add(pid)
382377

383-
if self.opts.services:
384-
for stale_service_name in sorted(stale_service_names):
385-
print(stale_service_name)
386-
return 0
387-
388-
for pid in sorted(stale_pids):
389-
print_cmd(pid)
378+
if self.opts.reboothint or self.opts.services:
379+
# Services whose binary belongs to a NEED_REBOOT package (e.g.
380+
# dbus-broker) are unsafe to restart even when stale due to a
381+
# non-NEED_REBOOT dependency like libselinux.
382+
reboot_service_names = set()
383+
for svc, pids in stale_service_pids.items():
384+
for pid in pids:
385+
try:
386+
exe_path = os.readlink('/proc/%d/exe' % pid)
387+
if exe_path.endswith(' (deleted)'):
388+
exe_path = exe_path[:-len(' (deleted)')]
389+
exe_pkg = owning_pkg_fn(exe_path)
390+
if exe_pkg and exe_pkg.name in installed_need_reboot_pkgs:
391+
reboot_service_names.add(svc)
392+
break # No need to check other PIDs for this service
393+
except OSError:
394+
# /proc/PID/exe may no longer exist if the process exited.
395+
pass
396+
for svc in reboot_service_names:
397+
stale_service_pids.pop(svc, None)
398+
399+
if self.opts.reboothint:
400+
if need_reboot or reboot_service_names:
401+
if need_reboot:
402+
print(_('Core libraries or services have been updated '
403+
'since boot-up:'))
404+
for name in sorted(need_reboot):
405+
print(' * %s' % name)
406+
if reboot_service_names:
407+
print(_('The following services cannot be safely '
408+
'restarted and require a reboot:'))
409+
for name in sorted(reboot_service_names):
410+
print(' * %s' % name)
411+
print()
412+
print(_('Reboot is required to fully utilize these updates.'))
413+
print(_('More information:'),
414+
'https://access.redhat.com/solutions/27943')
415+
raise dnf.exceptions.Error() # Sets exit code 1
416+
else:
417+
print(_('No core libraries or services have been updated '
418+
'since boot-up.'))
419+
print(_('Reboot should not be necessary.'))
420+
return None
421+
elif self.opts.services:
422+
for stale_service_name in sorted(stale_service_pids.keys()):
423+
print(stale_service_name)
424+
else:
425+
# default mode
426+
for pid in sorted(stale_pids):
427+
print_cmd(pid)

0 commit comments

Comments
 (0)