@@ -130,41 +130,41 @@ def print_cmd(pid):
130130
131131
132132def 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
169169def 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