1454 lines
40 KiB
Perl
Executable File
1454 lines
40 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
# nagios: -epn
|
|
|
|
# needrestart - Restart daemons after library updates.
|
|
#
|
|
# Authors:
|
|
# Thomas Liske <thomas@fiasko-nw.net>
|
|
#
|
|
# Copyright Holder:
|
|
# 2013 - 2022 (C) Thomas Liske [http://fiasko-nw.net/~thomas/]
|
|
#
|
|
# License:
|
|
# This program 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 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 package; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
|
|
use Cwd qw(realpath);
|
|
use Getopt::Std;
|
|
use NeedRestart;
|
|
use NeedRestart::UI;
|
|
use NeedRestart::Interp;
|
|
use NeedRestart::Kernel;
|
|
use NeedRestart::uCode;
|
|
use NeedRestart::Utils;
|
|
use Sort::Naturally;
|
|
use Locale::TextDomain 'needrestart';
|
|
use List::Util qw(sum);
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
$|++;
|
|
$Getopt::Std::STANDARD_HELP_VERSION++;
|
|
|
|
my $LOGPREF = '[main]';
|
|
my $is_systemd = -d q(/run/systemd/system);
|
|
my $is_runit = -e q(/run/runit.stopit);
|
|
my $is_tty = (-t *STDERR || -t *STDOUT || -t *STDIN);
|
|
my $is_vm;
|
|
my $is_container;
|
|
|
|
if($is_systemd && -x q(/usr/bin/systemd-detect-virt)) {
|
|
# check if we are inside of a vm
|
|
my $ret = system(qw(/usr/bin/systemd-detect-virt --vm --quiet));
|
|
unless($? == -1 || $? & 127) {
|
|
$is_vm = ($? >> 8) == 0;
|
|
}
|
|
|
|
# check if we are inside of a container
|
|
$ret = system(qw(/usr/bin/systemd-detect-virt --container --quiet));
|
|
unless($? == -1 || $? & 127) {
|
|
$is_container = ($? >> 8) == 0;
|
|
}
|
|
}
|
|
elsif(eval "use ImVirt; 1;") {
|
|
require ImVirt;
|
|
ImVirt->import();
|
|
my $imvirt = ImVirt::imv_get(ImVirt->IMV_PROB_DEFAULT);
|
|
|
|
$is_vm = $imvirt ne ImVirt->IMV_PHYSICAL;
|
|
$is_container = $imvirt eq ImVirt->IMV_CONTAINER;
|
|
}
|
|
elsif (-r "/proc/1/environ") {
|
|
# check if we are inside of a container (fallback)
|
|
local $/;
|
|
open(HENV, '<', '/proc/1/environ');
|
|
$is_container = scalar(grep {/^container=/;} unpack("(Z*)*", <HENV>));
|
|
close(HENV)
|
|
}
|
|
|
|
sub HELP_MESSAGE {
|
|
print <<USG;
|
|
Usage:
|
|
|
|
needrestart [-vn] [-c <cfg>] [-r <mode>] [-f <fe>] [-u <ui>] [-bkl]
|
|
|
|
-v be more verbose
|
|
-q be quiet
|
|
-m <mode> set detail level
|
|
e (e)asy mode
|
|
a (a)dvanced mode
|
|
u (u)buntu mode for the APT hook, see /usr/share/doc/needrestart/README.Ubuntu
|
|
-n set default answer to 'no'
|
|
-c <cfg> config filename
|
|
-r <mode> set restart mode
|
|
l (l)ist only
|
|
i (i)nteractive restart
|
|
a (a)utomatically restart
|
|
-b enable batch mode
|
|
-p enable nagios plugin mode
|
|
-f <fe> override debconf frontend (DEBIAN_FRONTEND, debconf(7))
|
|
-t <seconds> tolerate interpreter process start times within this value
|
|
-u <ui> use preferred UI package (-u ? shows available packages)
|
|
|
|
By using the following options only the specified checks are performed:
|
|
-k check for obsolete kernel
|
|
-l check for obsolete libraries
|
|
-w check for obsolete CPU microcode
|
|
|
|
--help show this help
|
|
--version show version information
|
|
|
|
USG
|
|
}
|
|
|
|
sub VERSION_MESSAGE {
|
|
print <<LIC;
|
|
|
|
needrestart $NeedRestart::VERSION - Restart daemons after library updates.
|
|
|
|
Authors:
|
|
Thomas Liske <thomas\@fiasko-nw.net>
|
|
|
|
Copyright Holder:
|
|
2013 - 2022 (C) Thomas Liske [http://fiasko-nw.net/~thomas/]
|
|
|
|
Upstream:
|
|
https://github.com/liske/needrestart
|
|
|
|
This program 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
LIC
|
|
#/
|
|
}
|
|
|
|
sub get_cgroup_unit {
|
|
my $pid = shift;
|
|
my $verbosity = shift;
|
|
if(open(HCGROUP, qq(/proc/$pid/cgroup))) {
|
|
my ($value) = map {
|
|
chomp;
|
|
my ($id, $type, $value) = split(/:/);
|
|
if($id != 0 && $type ne q(name=systemd)) {
|
|
();
|
|
}
|
|
else {
|
|
($value);
|
|
}
|
|
} <HCGROUP>;
|
|
close(HCGROUP);
|
|
return $value;
|
|
}
|
|
return;
|
|
}
|
|
|
|
our %nrconf = (
|
|
verbosity => 1,
|
|
hook_d => '/etc/needrestart/hook.d',
|
|
notify_d => '/etc/needrestart/notify.d',
|
|
restart_d => '/etc/needrestart/restart.d',
|
|
sendnotify => 1,
|
|
restart => 'i',
|
|
defno => 0,
|
|
ui_mode => 'a',
|
|
systemctl_combine => 0,
|
|
blacklist => [],
|
|
blacklist_interp => [],
|
|
blacklist_rc => [],
|
|
blacklist_mappings => [],
|
|
override_rc => {},
|
|
override_cont => {},
|
|
skip_mapfiles => -1,
|
|
interpscan => 1,
|
|
kernelhints => 1,
|
|
kernelfilter => qr(.),
|
|
ucodehints => 1,
|
|
q(nagios-status) => {
|
|
services => 1,
|
|
kernel => 2,
|
|
ucode => 2,
|
|
sessions => 2,
|
|
containers => 1,
|
|
},
|
|
has_pam_systemd => 1,
|
|
tolerance => 2,
|
|
);
|
|
|
|
# backup ARGV (required for Debconf)
|
|
my @argv = @ARGV;
|
|
|
|
our $opt_c = '/etc/needrestart/needrestart.conf';
|
|
our $opt_v;
|
|
our $opt_r;
|
|
our $opt_n;
|
|
our $opt_m;
|
|
our $opt_b;
|
|
our $opt_f;
|
|
our $opt_k;
|
|
our $opt_l;
|
|
our $opt_p;
|
|
our $opt_q;
|
|
our $opt_t;
|
|
our $opt_u;
|
|
our $opt_w;
|
|
unless(getopts('c:vr:nm:bf:klpqt:u:w')) {
|
|
HELP_MESSAGE;
|
|
exit 1;
|
|
}
|
|
|
|
# Ubuntu: adjust the defaults when called through APT
|
|
if (defined($opt_m) and $opt_m eq 'u') {
|
|
$nrconf{restart} = undef;
|
|
}
|
|
|
|
|
|
# disable exiting and STDOUT in Getopt::Std for further use of getopts
|
|
$Getopt::Std::STANDARD_HELP_VERSION = undef;
|
|
|
|
# restore ARGV
|
|
@ARGV = @argv;
|
|
|
|
die "ERROR: Could not read config file '$opt_c'!\n" unless(-r $opt_c || $opt_b);
|
|
|
|
# override debconf frontend
|
|
$ENV{DEBIAN_FRONTEND} = $opt_f if($opt_f);
|
|
my $debian_noninteractive = (exists($ENV{DEBIAN_FRONTEND}) && $ENV{DEBIAN_FRONTEND} eq 'noninteractive');
|
|
|
|
# be quiet
|
|
if($opt_q) {
|
|
$nrconf{verbosity} = 0;
|
|
}
|
|
# be verbose
|
|
elsif($opt_v) {
|
|
$nrconf{verbosity} = 2;
|
|
}
|
|
|
|
# slurp config file
|
|
print STDERR "$LOGPREF eval $opt_c\n" if($nrconf{verbosity} > 1);
|
|
eval do {
|
|
local $/;
|
|
open my $fh, $opt_c or die "ERROR: $!\n";
|
|
my $cfg = <$fh>;
|
|
close($fh);
|
|
$cfg;
|
|
};
|
|
die "Error parsing $opt_c: $@" if($@);
|
|
|
|
if (exists($ENV{NEEDRESTART_UI})) {
|
|
$nrconf{ui} = $ENV{NEEDRESTART_UI};
|
|
}
|
|
|
|
# fallback to stdio on verbose mode
|
|
$nrconf{ui} = qq(NeedRestart::UI::stdio) if($nrconf{verbosity} > 1);
|
|
|
|
$opt_m = $nrconf{ui_mode} unless(defined($opt_m));
|
|
die "ERROR: Unknown UI mode '$opt_m'!\n" unless($opt_m =~ /^(e|a|u)$/);
|
|
|
|
if ($opt_m eq 'u') {
|
|
if(defined $nrconf{restart}) {
|
|
print "Disabling Ubuntu mode, explicit restart mode configured";
|
|
$opt_m = $nrconf{ui_mode};
|
|
} elsif (defined $nrconf{ui}) {
|
|
print "Disabling Ubuntu mode, explicit UI configured";
|
|
$opt_m = $nrconf{ui_mode};
|
|
$nrconf{restart} = 'i';
|
|
} else {
|
|
$nrconf{restart} = 'a';
|
|
$nrconf{ui} = 'NeedRestart::UI::Ubuntu';
|
|
}
|
|
}
|
|
|
|
die "Hook directory '$nrconf{hook_d}' is invalid!\n" unless(-d $nrconf{hook_d} || $opt_b);
|
|
$opt_r = $ENV{NEEDRESTART_MODE} if(!defined($opt_r) && exists($ENV{NEEDRESTART_MODE}));
|
|
$opt_r = $nrconf{restart} unless(defined($opt_r));
|
|
die "ERROR: Unknown restart option '$opt_r'!\n" unless($opt_r =~ /^(l|i|a)$/);
|
|
$is_tty = 0 if($opt_r eq 'i' && $debian_noninteractive);
|
|
$opt_r = 'l' if(!$is_tty && $opt_r eq 'i');
|
|
$opt_r = 'l' if($opt_m eq 'e');
|
|
|
|
$opt_t = $nrconf{tolerance} unless(defined($opt_t));
|
|
|
|
$nrconf{defno}++ if($opt_n);
|
|
$opt_b++ if($opt_p);
|
|
|
|
# print version in verbose mode
|
|
print STDERR "$LOGPREF needrestart v$NeedRestart::VERSION\n" if($nrconf{verbosity} > 1);
|
|
|
|
# running mode (user or root)
|
|
my $uid = $<;
|
|
if($uid) {
|
|
if($opt_p) {
|
|
print "UNKN - This plugin needs to be run as root!\n";
|
|
exit 3;
|
|
}
|
|
|
|
print STDERR "$LOGPREF running in user mode\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF running in root mode\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
|
|
# get current runlevel, fallback to '2'
|
|
my $runlevel = `who -r` || '';
|
|
chomp($runlevel);
|
|
$runlevel = 2 unless($runlevel =~ s/^.+run-level (\S)\s.+$/$1/);
|
|
|
|
# get UI
|
|
if(defined($opt_u)) {
|
|
if ($opt_u eq '?') {
|
|
print STDERR join("\n\t", __(q(Available UI packages:)), needrestart_ui_list($nrconf{verbosity}, $nrconf{ui}))."\n";
|
|
exit 0;
|
|
}
|
|
else {
|
|
$nrconf{ui} = $opt_u;
|
|
}
|
|
}
|
|
|
|
if (!$is_tty) {
|
|
$nrconf{ui} = 'NeedRestart::UI::Stdio';
|
|
}
|
|
|
|
my $ui = ($opt_b ? NeedRestart::UI->new(0) : needrestart_ui($nrconf{verbosity}, $nrconf{ui}));
|
|
die "Error: no UI class available!\n" unless(defined($ui));
|
|
|
|
# Disable UI interactiveness
|
|
$ui->interactive(0) if ($ui->can("interactive") && $debian_noninteractive);
|
|
|
|
# enable/disable checks
|
|
unless(defined($opt_k) || defined($opt_l) || defined($opt_w)) {
|
|
$opt_k = ($uid ? undef : 1);
|
|
$opt_l = 1;
|
|
$opt_w = ($uid ? undef : $nrconf{ucodehints});
|
|
}
|
|
|
|
sub parse_lsbinit($) {
|
|
my $rc = '/etc/init.d/'.shift;
|
|
|
|
# ignore upstart-job magic
|
|
if(-l $rc && readlink($rc) eq '/lib/init/upstart-job') {
|
|
print STDERR "$LOGPREF ignoring $rc since it is a converted upstart job\n" if($nrconf{verbosity} > 1);
|
|
return ();
|
|
}
|
|
|
|
open(HLSB, '<', $rc) || die "Can't open $rc: $!\n";
|
|
my %lsb;
|
|
my $found_lsb;
|
|
my %chkconfig;
|
|
my $found_chkconfig;
|
|
while(my $line = <HLSB>) {
|
|
chomp($line);
|
|
|
|
unless($found_chkconfig) {
|
|
if($line =~ /^# chkconfig: (\d+) /) {
|
|
$chkconfig{runlevels} = $1;
|
|
$found_chkconfig++
|
|
}
|
|
}
|
|
elsif($line =~ /^# (\S+): (.+)$/) {
|
|
$chkconfig{lc($1)} = $2;
|
|
}
|
|
|
|
unless($found_lsb) {
|
|
$found_lsb++ if($line =~ /^### BEGIN INIT INFO/);
|
|
next;
|
|
}
|
|
elsif($line =~ /^### END INIT INFO/) {
|
|
last;
|
|
}
|
|
|
|
$lsb{lc($1)} = $2 if($line =~ /^# ([^:]+):\s+(.+)$/);
|
|
}
|
|
|
|
# convert chkconfig tags to LSB tags
|
|
if($found_chkconfig && !$found_lsb) {
|
|
print STDERR "$LOGPREF $rc is missing LSB tags, found chkconfig tags instead\n" if($nrconf{verbosity} > 1);
|
|
|
|
$found_lsb++;
|
|
$lsb{pidfiles} = [$chkconfig{pidfile}];
|
|
$lsb{q(default-start)} = $chkconfig{runlevels};
|
|
}
|
|
|
|
unless($found_lsb) {
|
|
print STDERR "WARNING: $rc has no LSB tags!\n" unless(%lsb);
|
|
return ();
|
|
}
|
|
|
|
# pid file heuristic
|
|
unless(exists($lsb{pidfiles})) {
|
|
my $found = 0;
|
|
my %pidfiles;
|
|
while(my $line = <HLSB>) {
|
|
if($line =~ m@(\S*/run/[^/]+.pid)@ && -r $1) {
|
|
$pidfiles{$1}++;
|
|
$found++;
|
|
}
|
|
}
|
|
$lsb{pidfiles} = [keys %pidfiles] if($found);
|
|
}
|
|
close(HLSB);
|
|
|
|
return %lsb;
|
|
}
|
|
|
|
print STDERR "$LOGPREF systemd detected\n" if($nrconf{verbosity} > 1 && $is_systemd);
|
|
print STDERR "$LOGPREF vm detected\n" if($nrconf{verbosity} > 1 && $is_vm);
|
|
print STDERR "$LOGPREF container detected\n" if($nrconf{verbosity} > 1 && $is_container);
|
|
|
|
sub systemd_refuse_restart {
|
|
my $svc = shift;
|
|
|
|
my $systemctl = nr_fork_pipe($nrconf{verbosity} > 1, qq(systemctl), qq(show), qq(--property=RefuseManualStop), $svc);
|
|
my $ret = <$systemctl>;
|
|
close($systemctl);
|
|
|
|
if($ret && $ret =~ /^RefuseManualStop=yes/) {
|
|
print STDERR "$LOGPREF systemd refuses restarts of $svc\n" if($nrconf{verbosity} > 1);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
my @systemd_restart;
|
|
sub restart_cmd($) {
|
|
my $rc = shift;
|
|
|
|
my $restcmd = "$nrconf{restart_d}/$rc";
|
|
if(-x $restcmd) {
|
|
print STDERR "$LOGPREF using restart.d file $rc\n" if($nrconf{verbosity} > 1);
|
|
($restcmd);
|
|
}
|
|
elsif($rc =~ /.+\.service$/) {
|
|
if($nrconf{systemctl_combine}) {
|
|
push(@systemd_restart, $rc);
|
|
();
|
|
}
|
|
else {
|
|
(qw(systemctl restart), $rc);
|
|
}
|
|
}
|
|
else {
|
|
if($is_systemd) {
|
|
if($nrconf{systemctl_combine}) {
|
|
push(@systemd_restart, qq($rc.service));
|
|
();
|
|
}
|
|
else {
|
|
(qw(systemctl restart), qq($rc.service));
|
|
}
|
|
}
|
|
elsif($is_runit && -d qq(/etc/sv/$rc)) {
|
|
if(-e qq(/etc/service/$rc)) {
|
|
(qw(sv restart), $rc);
|
|
}
|
|
else {
|
|
(q(service), $rc, q(restart));
|
|
}
|
|
}
|
|
else {
|
|
(q(invoke-rc.d), $rc, q(restart));
|
|
}
|
|
}
|
|
}
|
|
|
|
# map UID to username (cached)
|
|
my %uidcache;
|
|
sub uid2name($) {
|
|
my $uid = shift;
|
|
|
|
return $uidcache{$uid} if(exists($uidcache{$uid}));
|
|
|
|
return $uidcache{$uid} = getpwuid($uid) || $uid;
|
|
}
|
|
|
|
|
|
my %nagios = (
|
|
# kernel
|
|
kstr => q(unknown),
|
|
kret => 3,
|
|
kperf => q(U),
|
|
|
|
# uCode
|
|
mstr => q(unknown),
|
|
mret => 3,
|
|
mperf => q(U),
|
|
|
|
# services
|
|
sstr => q(unknown),
|
|
sret => 3,
|
|
sperf => q(U),
|
|
|
|
# sessions
|
|
ustr => q(unknown),
|
|
uret => 3,
|
|
uperf => q(U),
|
|
);
|
|
print "NEEDRESTART-VER: $NeedRestart::VERSION\n" if($opt_b && !$opt_p);
|
|
|
|
my %restart;
|
|
my %sessions;
|
|
my @guests;
|
|
my @easy_hints;
|
|
|
|
if(defined($opt_l)) {
|
|
my @ign_pids=($$, getppid());
|
|
|
|
# inspect only pids
|
|
my $ptable = nr_ptable();
|
|
|
|
# find session parent
|
|
sub findppid($@) {
|
|
my $uid = shift;
|
|
my ($pid, @pids) = @_;
|
|
|
|
if($ptable->{$pid}->{ppid} == 1) {
|
|
return $pid
|
|
if($ptable->{$pid}->{uid} == $uid);
|
|
|
|
return undef;
|
|
}
|
|
|
|
foreach my $pid (@pids) {
|
|
my $ppid = &findppid($uid, $pid);
|
|
|
|
return $ppid if($ppid);
|
|
}
|
|
|
|
return $pid;
|
|
}
|
|
|
|
$ui->progress_prep(scalar keys %$ptable, __ 'Scanning processes...');
|
|
my %stage2;
|
|
for my $pid (sort {$a <=> $b} keys %$ptable) {
|
|
$ui->progress_step;
|
|
|
|
# user-mode: skip foreign processes
|
|
next if($uid && $ptable->{$pid}->{uid} != $uid);
|
|
|
|
# skip myself
|
|
next if(grep {$pid == $_} @ign_pids);
|
|
|
|
my $restart = 0;
|
|
my $exe = nr_readlink($pid);
|
|
|
|
# ignore kernel threads
|
|
next unless(defined($exe));
|
|
|
|
# orphaned binary
|
|
$restart++ if (defined($exe) && $exe =~ s/ \(deleted\)$//); # Linux
|
|
$restart++ if (defined($exe) && $exe =~ s/^\(deleted\)//); # Linux VServer
|
|
print STDERR "$LOGPREF #$pid uses obsolete binary $exe\n" if($restart && $nrconf{verbosity} > 1);
|
|
|
|
# ignore blacklisted binaries
|
|
next if(grep { $exe =~ /$_/; } @{$nrconf{blacklist}});
|
|
|
|
# Sync $exe with the initial value from Proc::ProcessTable to prevent race
|
|
# conditions in later checks.
|
|
if(defined($ptable->{$pid}->{exec})) {
|
|
$exe = $ptable->{$pid}->{exec};
|
|
}
|
|
# Proc::ProcessTable's exec field is undef if the file is not accessible in
|
|
# the root mountns, so the value of $exe is used instead.
|
|
else {
|
|
$ptable->{$pid}->{exec} = $exe;
|
|
}
|
|
|
|
# read file mappings (Linux 2.0+)
|
|
unless($restart) {
|
|
if(open(HMAP, '<', "/proc/$pid/maps")) {
|
|
while(<HMAP>) {
|
|
chomp;
|
|
my ($maddr, $mperm, $moffset, $mdev, $minode, $path) = split(/\s+/, $_, 6);
|
|
|
|
# skip special handles and non-executable mappings
|
|
next unless(defined($path) && $minode != 0 && $path ne '' && $mperm =~ /x/);
|
|
|
|
# skip special device paths
|
|
next if(scalar grep { $path =~ /$_/; } @{$nrconf{blacklist_mappings}});
|
|
|
|
# removed executable mapped files
|
|
if($path =~ s/ \(deleted\)$// || # Linux
|
|
$path =~ s/^\(deleted\)//) { # Linux VServer
|
|
print STDERR "$LOGPREF #$pid uses deleted $path\n" if($nrconf{verbosity} > 1);
|
|
$restart++;
|
|
last;
|
|
}
|
|
|
|
# check for outdated lib mappings
|
|
unless($nrconf{skip_mapfiles} == 1) {
|
|
$maddr =~ s/^0+([^-])/$1/;
|
|
$maddr =~ s/-0+(.)/-$1/;
|
|
my @paths = ("/proc/$pid/map_files/$maddr", "/proc/$pid/root/$path");
|
|
my ($testp) = grep { -e $_; } @paths;
|
|
unless($testp) {
|
|
unless($nrconf{skip_mapfiles} == -1) {
|
|
print STDERR "$LOGPREF #$pid uses non-existing $path\n" if($nrconf{verbosity} > 1);
|
|
$restart++;
|
|
last;
|
|
}
|
|
next;
|
|
}
|
|
|
|
# get on-disk info
|
|
my ($sdev, $sinode) = stat($testp);
|
|
my @sdevs = (
|
|
# glibc gnu_dev_* definition from sysmacros.h
|
|
sprintf("%02x:%02x", (($sdev >> 8) & 0xfff) | (($sdev >> 32) & ~0xfff), (($sdev & 0xff) | (($sdev >> 12) & ~0xff))),
|
|
# Traditional definition of major(3) and minor(3)
|
|
sprintf("%02x:%02x", $sdev >> 8, $sdev & 0xff),
|
|
|
|
# kFreeBSD: /proc/<pid>/maps does not contain device IDs
|
|
qq(00:00)
|
|
);
|
|
|
|
# Don't compare device numbers on anon filesystems
|
|
# w/o a backing device (like OpenVZ's simfs).
|
|
my $major = (($sdev >> 8) & 0xfff) | (($sdev >> 32) & ~0xfff);
|
|
$mdev = "00:00"
|
|
if ($major == 0 || $major == 144 || $major == 145 || $major == 146);
|
|
|
|
# compare maps content vs. on-disk
|
|
unless($minode eq $sinode && ((grep {$mdev eq $_} @sdevs) ||
|
|
# BTRFS breaks device ID mapping completely...
|
|
# ignoring unnamed device IDs for now
|
|
$mdev =~ /^00:/)) {
|
|
print STDERR "$LOGPREF #$pid uses obsolete $path\n" if($nrconf{verbosity} > 1);
|
|
$restart++;
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
close(HMAP);
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF #$pid could not open maps: $!\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
}
|
|
|
|
unless($restart || !$nrconf{interpscan}) {
|
|
$restart++ if(needrestart_interp_check($nrconf{verbosity} > 1, $pid, $exe, $nrconf{blacklist_interp}, $opt_t));
|
|
}
|
|
|
|
# handle containers (LXC, docker, etc.)
|
|
next if($restart && needrestart_cont_check($nrconf{verbosity} > 1, $pid, $exe));
|
|
|
|
# restart needed?
|
|
next unless($restart);
|
|
|
|
# handle user sessions
|
|
if($ptable->{$pid}->{ttydev} ne '' && (!$is_systemd || !$nrconf{has_pam_systemd})) {
|
|
my $ttydev = realpath( $ptable->{$pid}->{ttydev} );
|
|
print STDERR "$LOGPREF #$pid part of user session: uid=$ptable->{$pid}->{uid} sess=$ttydev\n" if($nrconf{verbosity} > 1);
|
|
push(@{ $sessions{ $ptable->{$pid}->{uid} }->{ $ttydev }->{ $ptable->{$pid}->{fname} } }, $pid);
|
|
|
|
# add session processes to stage2 only in user mode
|
|
$stage2{$pid} = $exe if($uid);
|
|
|
|
next;
|
|
}
|
|
|
|
# find parent process
|
|
my $ppid = $ptable->{$pid}->{ppid};
|
|
if($ppid != $pid && $ppid > 1 && !$uid) {
|
|
print STDERR "$LOGPREF #$pid is a child of #$ppid\n" if($nrconf{verbosity} > 1);
|
|
|
|
if($uid && $ptable->{$ppid}->{uid} != $uid) {
|
|
print STDERR "$LOGPREF #$ppid is a foreign process\n" if($nrconf{verbosity} > 1);
|
|
$stage2{$pid} = $exe;
|
|
}
|
|
else {
|
|
unless(exists($stage2{$ppid})) {
|
|
my $pexe = nr_readlink($ppid);
|
|
# ignore kernel threads
|
|
next unless(defined($pexe));
|
|
|
|
$stage2{$ppid} = $pexe;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF #$pid is not a child\n" if($nrconf{verbosity} > 1 && !$uid);
|
|
$stage2{$pid} = $exe;
|
|
}
|
|
}
|
|
$ui->progress_fin;
|
|
|
|
if(scalar keys %stage2 && !$uid) {
|
|
$ui->progress_prep(scalar keys %stage2, __ 'Scanning candidates...');
|
|
PIDLOOP: foreach my $pid (sort {$a <=> $b} keys %stage2) {
|
|
$ui->progress_step;
|
|
|
|
# skip myself
|
|
next if(grep {$pid == $_} @ign_pids);
|
|
|
|
my $exe = nr_readlink($pid);
|
|
$exe =~ s/ \(deleted\)$//; # Linux
|
|
$exe =~ s/^\(deleted\)//; # Linux VServer
|
|
print STDERR "$LOGPREF #$pid exe => $exe\n" if($nrconf{verbosity} > 1);
|
|
|
|
# try to find interpreter source file
|
|
($exe) = (needrestart_interp_source($nrconf{verbosity} > 1, $pid, $exe), $exe);
|
|
|
|
# ignore blacklisted binaries
|
|
next if(grep { $exe =~ /$_/; } @{$nrconf{blacklist}});
|
|
|
|
if($is_systemd) {
|
|
# systemd manager
|
|
if($pid == 1 && $exe =~ m@^(/usr)?/lib/systemd/systemd@) {
|
|
print STDERR "$LOGPREF #$pid is systemd manager\n" if($nrconf{verbosity} > 1);
|
|
$restart{q(systemd-manager)}++;
|
|
next;
|
|
}
|
|
|
|
# get unit name from /proc/<pid>/cgroup
|
|
my $unit = get_cgroup_unit($pid, $nrconf{verbosity});
|
|
next unless (defined($unit));
|
|
|
|
if($unit =~ m@/user-(\d+)\.slice/session-(\d+)\.scope@) {
|
|
print STDERR "$LOGPREF #$pid part of user session: uid=$1 sess=$2\n" if($nrconf{verbosity} > 1);
|
|
push(@{ $sessions{$1}->{"session #$2"}->{ $ptable->{$pid}->{fname} } }, $pid);
|
|
next;
|
|
}
|
|
if($unit =~ m@/user\@(\d+)\.service@) {
|
|
print STDERR "$LOGPREF #$pid part of user manager service: uid=$1\n" if($nrconf{verbosity} > 1);
|
|
push(@{ $sessions{$1}->{'user manager service'}->{ $ptable->{$pid}->{fname} } }, $pid);
|
|
next;
|
|
}
|
|
if($unit =~ m@/machine.slice/machine.qemu(.*).scope@) {
|
|
for my $cmdlineidx (0 .. $#{$ptable->{$pid}->{cmdline}} ) {
|
|
if ( ${$ptable->{$pid}->{cmdline}}[$cmdlineidx] eq "-name") {
|
|
foreach ( split(/,/, ${$ptable->{$pid}->{cmdline}}[$cmdlineidx+1]) ) {
|
|
if ( index($_, "guest=") == 0 ) {
|
|
my @namearg = split(/=/, $_, 2);
|
|
if ($#{namearg} == 1) {
|
|
print STDERR "$LOGPREF #$pid detected as VM guest '$namearg[1]' in group '$unit'\n" if($nrconf{verbosity} > 1);
|
|
push(@guests, __x("'{name}' with pid {pid}", name => $namearg[1], pid=>$pid) );
|
|
}
|
|
next PIDLOOP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
print STDERR "$LOGPREF #$pid detected as VM guest with unknown name in group '$unit'\n" if($nrconf{verbosity} > 1);
|
|
push(@guests, __x("'Unknown VM' with pid {pid}", pid=>$pid) );
|
|
next;
|
|
}
|
|
my $rc;
|
|
if($unit =~ m@/([^/]+\.service)$@) {
|
|
$rc = $1;
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF #$pid unexpected cgroup '$unit'\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
|
|
if($rc) {
|
|
print STDERR "$LOGPREF #$pid is $rc\n" if($nrconf{verbosity} > 1);
|
|
$restart{$rc}++;
|
|
next;
|
|
}
|
|
|
|
# did not get the unit name, yet - try systemctl status
|
|
print STDERR "$LOGPREF /proc/$pid/cgroup: $!\n" if($nrconf{verbosity} > 1 && $!);
|
|
print STDERR "$LOGPREF trying systemctl status\n" if($nrconf{verbosity} > 1);
|
|
my $systemctl = nr_fork_pipe($nrconf{verbosity} > 1, qq(systemctl), qq(-n), qq(0), qq(--full), qq(status), $pid);
|
|
my $ret = <$systemctl>;
|
|
close($systemctl);
|
|
|
|
if(defined($ret) && $ret =~ /([^\s]+\.service)( |$)/) {
|
|
my $s = $1;
|
|
print STDERR "$LOGPREF #$pid is $s\n" if($nrconf{verbosity} > 1);
|
|
$restart{$s}++;
|
|
$s =~ s/\.service$//;
|
|
delete($restart{$s});
|
|
next;
|
|
}
|
|
}
|
|
else {
|
|
# sysv init
|
|
if($pid == 1 && $exe =~ m@^/sbin/init@) {
|
|
print STDERR "$LOGPREF #$pid is sysv init\n" if($nrconf{verbosity} > 1);
|
|
$restart{q(sysv-init)}++;
|
|
next;
|
|
}
|
|
}
|
|
|
|
my $pkg;
|
|
foreach my $hook (nsort <$nrconf{hook_d}/*>) {
|
|
print STDERR "$LOGPREF #$pid running $hook\n" if($nrconf{verbosity} > 1);
|
|
|
|
my $found = 0;
|
|
my $prun = nr_fork_pipe($nrconf{verbosity} > 1, $hook, ($nrconf{verbosity} > 1 ? qw(-v) : ()), $exe);
|
|
my @nopids;
|
|
while(<$prun>) {
|
|
chomp;
|
|
my @v = split(/\|/);
|
|
|
|
if($v[0] eq 'PACKAGE' && $v[1]) {
|
|
$pkg = $v[1];
|
|
print STDERR "$LOGPREF #$pid package: $v[1]\n" if($nrconf{verbosity} > 1);
|
|
next;
|
|
}
|
|
|
|
if($v[0] eq 'RC') {
|
|
my %lsb = parse_lsbinit($v[1]);
|
|
|
|
unless(%lsb && exists($lsb{'default-start'})) {
|
|
# If the script has no LSB tags we consider to call it later - they
|
|
# are broken anyway.
|
|
print STDERR "$LOGPREF no LSB headers found at $v[1]\n" if($nrconf{verbosity} > 1);
|
|
push(@nopids, $v[1]);
|
|
}
|
|
# In the run-levels S and 1 no daemons are being started (normally).
|
|
# We don't call any rc.d script not started in the current run-level.
|
|
elsif($lsb{'default-start'} =~ /$runlevel/) {
|
|
# If a pidfile has been found, try to look for the daemon and ignore
|
|
# any forked/detached childs (just a heuristic due Debian Bug#721810).
|
|
if(exists($lsb{pidfiles})) {
|
|
foreach my $pidfile (@{ $lsb{pidfiles} }) {
|
|
open(HPID, '<', "$pidfile") || next;
|
|
my $p = <HPID>;
|
|
close(HPID);
|
|
|
|
if(int($p) == $pid) {
|
|
print STDERR "$LOGPREF #$pid has been started by $v[1] - triggering\n" if($nrconf{verbosity} > 1);
|
|
$restart{$v[1]}++;
|
|
$found++;
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF no pidfile reference found at $v[1]\n" if($nrconf{verbosity} > 1);
|
|
push(@nopids, $v[1]);
|
|
}
|
|
}
|
|
else {
|
|
print STDERR "$LOGPREF #$pid rc.d script $v[1] should not start in the current run-level($runlevel)\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
# No perfect hit - call any rc scripts instead.
|
|
print STDERR "$LOGPREF #$pid running $hook no perfect hit found $found pids $#nopids\n" if($nrconf{verbosity} > 1);
|
|
if(!$found && $#nopids > -1) {
|
|
foreach my $rc (@nopids) {
|
|
if($is_systemd && exists($restart{"$rc.service"})) {
|
|
print STDERR "$LOGPREF #$pid rc.d script $rc seems to be superseded by $rc.service\n" if($nrconf{verbosity} > 1);
|
|
}
|
|
else {
|
|
$restart{$rc}++;
|
|
}
|
|
}
|
|
$found++;
|
|
}
|
|
|
|
last if($found);
|
|
}
|
|
}
|
|
$ui->progress_fin;
|
|
}
|
|
|
|
# List user's processes in user-mode
|
|
if($uid && scalar %stage2) {
|
|
my %fnames;
|
|
foreach my $pid (keys %stage2) {
|
|
push(@{$fnames{ $ptable->{$pid}->{fname} }}, $pid);
|
|
}
|
|
|
|
if($opt_b) {
|
|
print map { "NEEDRESTART-PID: $_=".join(',', @{ $fnames{$_} })."\n"; } nsort keys %fnames;
|
|
}
|
|
else {
|
|
$ui->notice(__ 'Your outdated processes:');
|
|
$ui->notice(join(', ',map { $_.'['.join(', ', @{ $fnames{$_} }).']'; } nsort keys %fnames));
|
|
}
|
|
}
|
|
}
|
|
|
|
# Apply rc/service blacklist
|
|
foreach my $rc (keys %restart) {
|
|
next unless(scalar grep { $rc =~ /$_/; } @{$nrconf{blacklist_rc}});
|
|
|
|
print STDERR "$LOGPREF $rc is blacklisted -> ignored\n" if($nrconf{verbosity} > 1);
|
|
delete($restart{$rc});
|
|
}
|
|
|
|
# Skip kernel stuff within container
|
|
if($is_container || needrestart_cont_check($nrconf{verbosity} > 1, 1, nr_readlink(1), 1)) {
|
|
print STDERR "$LOGPREF inside container, skipping kernel checks\n" if($nrconf{verbosity} > 1);
|
|
$opt_k = undef;
|
|
}
|
|
|
|
# Skip uCode stuff within container or vm
|
|
if($is_container || $is_vm || needrestart_cont_check($nrconf{verbosity} > 1, 1, nr_readlink(1), 1)) {
|
|
print STDERR "$LOGPREF inside container or vm, skipping microcode checks\n" if($nrconf{verbosity} > 1);
|
|
$opt_w = undef;
|
|
}
|
|
|
|
my ($ucode_result, %ucode_vars) = (NRM_UNKNOWN);
|
|
if(defined($opt_w)) {
|
|
($ucode_result, %ucode_vars) = ($nrconf{ucodehints} || $opt_w ? nr_ucode_check($nrconf{verbosity} > 1, $ui) : ());
|
|
}
|
|
|
|
if(defined($opt_k)) {
|
|
my ($kresult, %kvars) = ($nrconf{kernelhints} || $opt_b ? nr_kernel_check($nrconf{verbosity} > 1, $nrconf{kernelfilter}, $ui) : ());
|
|
|
|
if(defined($kresult)) {
|
|
if($opt_b) {
|
|
unless($opt_p) {
|
|
print "NEEDRESTART-KCUR: $kvars{KVERSION}\n";
|
|
print "NEEDRESTART-KEXP: $kvars{EVERSION}\n" if(defined($kvars{EVERSION}));
|
|
print "NEEDRESTART-KSTA: $kresult\n";
|
|
}
|
|
else {
|
|
$nagios{kstr} = $kvars{KVERSION};
|
|
if($kresult == NRK_VERUPGRADE) {
|
|
$nagios{kstr} .= "!=$kvars{EVERSION}";
|
|
$nagios{kret} = $nrconf{q(nagios-status)}->{kernel};
|
|
$nagios{kperf} = 2;
|
|
}
|
|
elsif($kresult == NRK_ABIUPGRADE) {
|
|
$nagios{kret} = $nrconf{q(nagios-status)}->{kernel};
|
|
$nagios{kperf} = 1;
|
|
}
|
|
elsif($kresult == NRK_NOUPGRADE) {
|
|
$nagios{kret} = 0;
|
|
$nagios{kperf} = 0;
|
|
}
|
|
|
|
if($nagios{kret} == 1) {
|
|
$nagios{kstr} .= " (!)";
|
|
}
|
|
elsif($nagios{kret} == 2) {
|
|
$nagios{kstr} .= " (!!)";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if($kresult == NRK_NOUPGRADE) {
|
|
unless($opt_m eq 'e') {
|
|
$ui->vspace();
|
|
$ui->notice(($kvars{ABIDETECT} ? __('Running kernel seems to be up-to-date.') : __('Running kernel seems to be up-to-date (ABI upgrades are not detected).')))
|
|
}
|
|
}
|
|
elsif($kresult == NRK_ABIUPGRADE) {
|
|
push(@easy_hints, __ 'an outdated kernel image') if($opt_m eq 'e');
|
|
|
|
if($nrconf{kernelhints} < 0) {
|
|
$ui->vspace();
|
|
$ui->notice(__x(
|
|
'The currently running kernel version is {kversion} and there is an ABI compatible upgrade pending.',
|
|
kversion => $kvars{KVERSION},
|
|
));
|
|
}
|
|
else {
|
|
$ui->announce_abi(%kvars);
|
|
}
|
|
}
|
|
elsif($kresult == NRK_VERUPGRADE) {
|
|
push(@easy_hints, __ 'an outdated kernel image') if($opt_m eq 'e');
|
|
|
|
if($nrconf{kernelhints} < 0) {
|
|
$ui->vspace();
|
|
$ui->notice(__x(
|
|
'The currently running kernel version is {kversion} which is not the expected kernel version {eversion}.',
|
|
kversion => $kvars{KVERSION},
|
|
eversion => $kvars{EVERSION},
|
|
));
|
|
}
|
|
else {
|
|
$ui->announce_ver(%kvars);
|
|
}
|
|
}
|
|
else {
|
|
$ui->vspace();
|
|
$ui->notice(__ 'Failed to retrieve available kernel versions.');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($opt_w) {
|
|
if($opt_b) {
|
|
unless($opt_p) {
|
|
print "NEEDRESTART-UCSTA: $ucode_result\n";
|
|
if($ucode_result != NRM_UNKNOWN) {
|
|
print "NEEDRESTART-UCCUR: $ucode_vars{CURRENT}\n";
|
|
print "NEEDRESTART-UCEXP: $ucode_vars{AVAIL}\n";
|
|
}
|
|
}
|
|
else {
|
|
if($ucode_result == NRM_OBSOLETE) {
|
|
$nagios{mstr} = "OBSOLETE";
|
|
$nagios{mret} = $nrconf{q(nagios-status)}->{ucode};
|
|
$nagios{mperf} = 1;
|
|
}
|
|
elsif($ucode_result == NRM_CURRENT) {
|
|
$nagios{mstr} = "CURRENT";
|
|
$nagios{mret} = 0;
|
|
$nagios{mperf} = 0;
|
|
}
|
|
|
|
if($nagios{mret} == 1) {
|
|
$nagios{mstr} .= " (!)";
|
|
}
|
|
elsif($nagios{mret} == 2) {
|
|
$nagios{mstr} .= " (!!)";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if($ucode_result == NRM_CURRENT) {
|
|
unless($opt_m eq 'e') {
|
|
$ui->vspace();
|
|
$ui->notice(__('The processor microcode seems to be up-to-date.'));
|
|
}
|
|
}
|
|
elsif($ucode_result == NRM_OBSOLETE) {
|
|
push(@easy_hints, __ 'outdated processor microcode') if($opt_m eq 'e');
|
|
|
|
if($nrconf{ucodehints}) {
|
|
$ui->announce_ucode(%ucode_vars);
|
|
}
|
|
}
|
|
else {
|
|
$ui->vspace();
|
|
$ui->notice(__ 'Failed to check for processor microcode upgrades.');
|
|
}
|
|
}
|
|
}
|
|
|
|
if(defined($opt_l) && !$uid) {
|
|
## SERVICES
|
|
$ui->vspace();
|
|
unless(scalar %restart) {
|
|
$ui->notice(__ 'No services need to be restarted.') unless($opt_b || $opt_m eq 'e');
|
|
if($opt_p) {
|
|
$nagios{sstr} = q(none);
|
|
$nagios{sret} = 0;
|
|
$nagios{sperf} = 0;
|
|
}
|
|
}
|
|
else {
|
|
if($opt_m eq 'e' && $opt_r ne 'i') {
|
|
push(@easy_hints, __ 'outdated binaries');
|
|
}
|
|
elsif($opt_b || $opt_r ne 'i') {
|
|
my @skipped_services;
|
|
my @refused_services;
|
|
|
|
$ui->notice(__ 'Services to be restarted:') if($opt_r eq 'l');
|
|
$ui->notice(__ 'Restarting services...') if($opt_r eq 'a');
|
|
if($opt_p) {
|
|
$nagios{sstr} = (scalar keys %restart);
|
|
$nagios{sret} = $nrconf{q(nagios-status)}->{services};
|
|
$nagios{sperf} = (scalar keys %restart);
|
|
|
|
if($nagios{sret} == 1) {
|
|
$nagios{sstr} .= " (!)";
|
|
}
|
|
elsif($nagios{sret} == 2) {
|
|
$nagios{sstr} .= " (!!)";
|
|
}
|
|
}
|
|
|
|
my ($self_unit) = get_cgroup_unit($$, $nrconf{verbosity});
|
|
foreach my $rc (sort { lc($a) cmp lc($b) } keys %restart) {
|
|
# always combine restarts in one systemctl command
|
|
local $nrconf{systemctl_combine} = 1 unless($opt_r eq 'l');
|
|
|
|
if($opt_b) {
|
|
print "NEEDRESTART-SVC: $rc\n" unless($opt_p);
|
|
next;
|
|
}
|
|
|
|
# record service which can not be restarted
|
|
if($is_systemd && systemd_refuse_restart($rc)) {
|
|
push(@refused_services, $rc);
|
|
next;
|
|
}
|
|
|
|
# don't restart greylisted services...
|
|
my $restart = !$nrconf{defno};
|
|
# Default to defer restarting the service that called needrestart,
|
|
# as it could result in needrestart committing suicide.
|
|
$restart = 0 if ($self_unit =~ /${rc}$/);
|
|
foreach my $re (keys %{$nrconf{override_rc}}) {
|
|
next unless($rc =~ /$re/);
|
|
|
|
$restart = $nrconf{override_rc}->{$re};
|
|
last;
|
|
}
|
|
# ...but complain about them
|
|
unless($restart) {
|
|
push(@skipped_services, $rc);
|
|
next;
|
|
}
|
|
|
|
my @cmd = restart_cmd($rc);
|
|
next unless($#cmd > -1);
|
|
|
|
$ui->command(join(' ', '', @cmd));
|
|
$ui->runcmd(sub {
|
|
system(@cmd) if($opt_r eq 'a');
|
|
});
|
|
}
|
|
|
|
unless($#systemd_restart == -1) {
|
|
my @cmd = (qq(systemctl), qq(restart), @systemd_restart);
|
|
$ui->command(join(' ', '', @cmd));
|
|
$ui->runcmd(sub {
|
|
system(@cmd) if($opt_r eq 'a');
|
|
});
|
|
}
|
|
|
|
@systemd_restart = ();
|
|
if($#skipped_services > -1) {
|
|
$ui->vspace();
|
|
$ui->notice(__ 'Service restarts being deferred:');
|
|
foreach my $rc (sort @skipped_services) {
|
|
my @cmd = restart_cmd($rc);
|
|
$ui->command(join(' ', '', @cmd)) if($#cmd > -1);
|
|
}
|
|
|
|
unless($#systemd_restart == -1) {
|
|
my @cmd = (qq(systemctl), qq(restart), @systemd_restart);
|
|
$ui->command(join(' ', '', @cmd));
|
|
}
|
|
}
|
|
|
|
# report services restarts refused by systemd
|
|
if($#refused_services > -1) {
|
|
$ui->vspace();
|
|
$ui->notice(__ 'Service restarts being refused by systemd:');
|
|
foreach my $rc (sort @refused_services) {
|
|
$ui->command(qq( $rc));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
my $o = 0;
|
|
my @skipped_services = keys %restart;
|
|
|
|
# filter service units which are refused to be restarted
|
|
my @refused_services;
|
|
my %rs = map {
|
|
my $rc = $_;
|
|
|
|
if($is_systemd) {
|
|
if(systemd_refuse_restart($rc)) {
|
|
push(@refused_services, $rc);
|
|
@skipped_services = grep { $_ ne $rc; } @skipped_services;
|
|
();
|
|
}
|
|
else {
|
|
($rc => 1);
|
|
}
|
|
}
|
|
else {
|
|
($rc => 1);
|
|
}
|
|
} keys %restart;
|
|
|
|
$ui->notice(__ 'Restarting services...');
|
|
$ui->query_pkgs(__('Services to be restarted:'), $nrconf{defno}, \%rs, $nrconf{override_rc},
|
|
sub {
|
|
# always combine restarts in one systemctl command
|
|
local $nrconf{systemctl_combine} = 1;
|
|
|
|
my $rc = shift;
|
|
@skipped_services = grep { $_ ne $rc; } @skipped_services;
|
|
|
|
my @cmd = restart_cmd($rc);
|
|
return unless($#cmd > -1);
|
|
|
|
$ui->command(join(' ', '', @cmd));
|
|
system(@cmd);
|
|
});
|
|
|
|
if($#systemd_restart > -1) {
|
|
my @cmd = (qw(systemctl restart), @systemd_restart);
|
|
|
|
$ui->command(join(' ', '', @cmd));
|
|
$ui->runcmd(sub {
|
|
system(@cmd);
|
|
});
|
|
}
|
|
|
|
@systemd_restart = ();
|
|
if($#skipped_services > -1) {
|
|
$ui->notice(__ 'Service restarts being deferred:');
|
|
foreach my $rc (sort @skipped_services) {
|
|
my @cmd = restart_cmd($rc);
|
|
$ui->command(join(' ', '', @cmd)) if($#cmd > -1);
|
|
}
|
|
|
|
unless($#systemd_restart == -1) {
|
|
my @cmd = (qq(systemctl), qq(restart), @systemd_restart);
|
|
$ui->command(join(' ', '', @cmd));
|
|
}
|
|
}
|
|
|
|
# report services restarts refused by systemd
|
|
if($#refused_services > -1) {
|
|
$ui->notice(__ 'Service restarts being refused by systemd:');
|
|
foreach my $rc (sort @refused_services) {
|
|
$ui->command(qq( $rc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
## CONTAINERS
|
|
$ui->vspace();
|
|
@systemd_restart = ();
|
|
my %conts = needrestart_cont_get($nrconf{verbosity} > 1);
|
|
unless(scalar %conts) {
|
|
$ui->notice(__ 'No containers need to be restarted.') unless($opt_b || $opt_m eq 'e');
|
|
if($opt_p) {
|
|
$nagios{cstr} = q(none);
|
|
$nagios{cret} = 0;
|
|
$nagios{cperf} = 0;
|
|
}
|
|
}
|
|
else {
|
|
if($opt_m eq 'e' && $opt_r ne 'i') {
|
|
push(@easy_hints, __ 'outdated containers');
|
|
}
|
|
elsif($opt_b || $opt_r ne 'i') {
|
|
my @skipped_containers;
|
|
|
|
$ui->notice(__ 'Containers to be restarted:') if($opt_r eq 'l');
|
|
$ui->notice(__ 'Restarting containers...') if($opt_r eq 'a' and $opt_m ne 'u');
|
|
if($opt_p) {
|
|
$nagios{cstr} = (scalar keys %conts);
|
|
$nagios{cret} = $nrconf{q(nagios-status)}->{containers};
|
|
$nagios{cperf} = (scalar keys %conts);
|
|
|
|
if($nagios{cret} == 1) {
|
|
$nagios{cstr} .= " (!)";
|
|
}
|
|
elsif($nagios{cret} == 2) {
|
|
$nagios{cstr} .= " (!!)";
|
|
}
|
|
}
|
|
|
|
foreach my $cont (sort { lc($a) cmp lc($b) } keys %conts) {
|
|
if($opt_b) {
|
|
print "NEEDRESTART-CONT: $cont\n" unless($opt_p);
|
|
next;
|
|
}
|
|
|
|
# don't restart greylisted containers...
|
|
# On Ubuntu mode, all containers are greylisted by default.
|
|
my $restart = !($nrconf{defno} or $opt_m eq 'u');
|
|
foreach my $re (keys %{$nrconf{override_cont}}) {
|
|
next unless($cont =~ /$re/);
|
|
|
|
$restart = $nrconf{override_cont}->{$re};
|
|
last;
|
|
}
|
|
# ...but complain about them
|
|
unless($restart) {
|
|
push(@skipped_containers, $cont);
|
|
next;
|
|
}
|
|
|
|
$ui->command(join(' ', '', @{ $conts{$cont} }));
|
|
$ui->runcmd(sub {
|
|
system(@{ $conts{$cont} }) if($opt_r eq 'a');
|
|
});
|
|
}
|
|
|
|
if($#skipped_containers > -1) {
|
|
$ui->notice(__ 'Container restarts being deferred:');
|
|
foreach my $cont (sort @skipped_containers) {
|
|
$ui->command(join(' ', '', @{ $conts{$cont} }));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
my $o = 0;
|
|
|
|
$ui->notice(__ 'Restarting containers...');
|
|
$ui->query_conts(__('Containers to be restarted:'), $nrconf{defno}, \%conts, $nrconf{override_cont},
|
|
sub {
|
|
my $cont = shift;
|
|
$ui->command(join(' ', '', @{ $conts{$cont} }));
|
|
system(@{ $conts{$cont} });
|
|
});
|
|
}
|
|
}
|
|
|
|
## SESSIONS
|
|
$ui->vspace();
|
|
# list and notify user sessions
|
|
unless(scalar keys %sessions) {
|
|
$ui->notice(__ 'No user sessions are running outdated binaries.') unless($opt_b || $opt_m eq 'e');
|
|
if($opt_p) {
|
|
$nagios{ustr} = 'none';
|
|
$nagios{uret} = 0;
|
|
$nagios{uperf} = 0;
|
|
}
|
|
}
|
|
else {
|
|
if($opt_m eq 'e') {
|
|
push(@easy_hints, __ 'outdated sessions');
|
|
}
|
|
else {
|
|
$ui->notice(__ 'User sessions running outdated binaries:');
|
|
}
|
|
if($opt_p) {
|
|
my $count = sum map { scalar keys %{ $sessions{$_} } } keys %sessions;
|
|
$nagios{ustr} = $count;
|
|
$nagios{uret} = $nrconf{q(nagios-status)}->{sessions};
|
|
$nagios{uperf} = $count;
|
|
|
|
if($nagios{uret} == 1) {
|
|
$nagios{ustr} .= " (!)";
|
|
}
|
|
elsif($nagios{uret} == 2) {
|
|
$nagios{ustr} .= " (!!)";
|
|
}
|
|
}
|
|
unless($opt_p || $opt_b) {
|
|
foreach my $uid (sort { ncmp(uid2name($a), uid2name($b)); } keys %sessions) {
|
|
foreach my $sess (sort keys %{ $sessions{$uid} }) {
|
|
my $fnames = join(', ',map { $_.'['.join(',', @{ $sessions{$uid}->{$sess}->{$_} }).']'; } nsort keys %{ $sessions{$uid}->{$sess} });
|
|
$ui->notice(' '.uid2name($uid)." @ $sess: $fnames") unless($opt_m eq 'e');
|
|
if($nrconf{sendnotify}) {
|
|
local %ENV;
|
|
|
|
$ENV{NR_UID} = $uid;
|
|
$ENV{NR_USERNAME} = uid2name($uid);
|
|
$ENV{NR_SESSION} = $sess;
|
|
$ENV{NR_SESSPPID} = findppid($uid, sort map { @$_; } values %{ $sessions{$uid}->{$sess} });
|
|
|
|
foreach my $bin (nsort <$nrconf{notify_d}/*>) {
|
|
next unless(-x $bin);
|
|
next if($bin =~ /(~|\.dpkg-[^.]+)$/);
|
|
|
|
print STDERR "$LOGPREF run $bin\n" if($nrconf{verbosity} > 1);
|
|
my $pipe = nr_fork_pipew($nrconf{verbosity} > 1, $bin);
|
|
print $pipe "$fnames\n";
|
|
last if(close($pipe));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
## GUESTS
|
|
$ui->vspace();
|
|
if (! @guests) {
|
|
$ui->notice(__ 'No VM guests are running outdated hypervisor (qemu) binaries on this host.') unless($opt_b || $opt_m eq 'e');
|
|
}
|
|
else {
|
|
if($opt_m eq 'e') {
|
|
push(@easy_hints, __ 'outdated VM guests');
|
|
}
|
|
else {
|
|
unless($opt_p || $opt_b) {
|
|
$ui->notice(__ 'VM guests are running outdated hypervisor (qemu) binaries on this host:');
|
|
foreach ( @guests ) {
|
|
$ui->notice(" $_");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# easy mode: print hint on outdated stuff
|
|
if(scalar @easy_hints) {
|
|
my $t = pop(@easy_hints);
|
|
my $h = join(', ', @easy_hints);
|
|
$ui->announce_ehint(EHINT => ($h ? join(' ', $h, __ 'and', '') : '') . $t);
|
|
}
|
|
|
|
my @sessions_list;
|
|
if(scalar %sessions) {
|
|
# build a sorted list of user @ session strings
|
|
#
|
|
# used in the nagios and batch outputs below
|
|
@sessions_list = map {
|
|
my $uid = $_;
|
|
my $user = uid2name($uid);
|
|
my @ret;
|
|
|
|
foreach my $sess (sort keys %{ $sessions{$uid} }) {
|
|
push(@ret, "$user \@ $sess");
|
|
}
|
|
|
|
@ret;
|
|
}
|
|
sort {
|
|
ncmp(uid2name($a), uid2name($b));
|
|
} keys %sessions
|
|
}
|
|
|
|
# nagios plugin output
|
|
if($opt_p) {
|
|
my %states = (
|
|
0 => q(OK),
|
|
1 => q(WARN),
|
|
2 => q(CRIT),
|
|
3 => q(UNKN),
|
|
);
|
|
my ($ret) = reverse sort
|
|
(($opt_k ? $nagios{kret} : ()), ($opt_w ? $nagios{mret} : ()),
|
|
($opt_l ? ($nagios{sret}, $nagios{cret}, $nagios{uret}) : ()));
|
|
|
|
print "$states{$ret} - ", join(', ',
|
|
($opt_k ? "Kernel: $nagios{kstr}" : ()),
|
|
($opt_w ? "Microcode: $nagios{mstr}" : ()),
|
|
($opt_l ? "Services: $nagios{sstr}" : ()),
|
|
($opt_l ? "Containers: $nagios{cstr}" : ()),
|
|
($opt_l ? "Sessions: $nagios{ustr}" : ()),
|
|
), '|', join(' ',
|
|
( ($opt_k && $nagios{kret} != 3) ? "Kernel=$nagios{kperf};0;;0;2" : ()),
|
|
( ($opt_w && $nagios{mret} != 3) ? "Microcode=$nagios{mperf};0;;0;1" : ()),
|
|
( ($opt_l && $nagios{sret} != 3) ? "Services=$nagios{sperf};;0;0" : ()),
|
|
( ($opt_l && $nagios{cret} != 3) ? "Containers=$nagios{cperf};;0;0" : ()),
|
|
( ($opt_l && $nagios{uret} != 3) ? "Sessions=$nagios{uperf};0;;0" : ()),
|
|
), "\n";
|
|
|
|
if(scalar %restart) {
|
|
print "Services:", join("\n- ", '', sort keys %restart), "\n";
|
|
}
|
|
|
|
my %conts = needrestart_cont_get($nrconf{verbosity} > 1);
|
|
if(scalar %conts) {
|
|
print "Containers:", join("\n- ", '', sort keys %conts), "\n";
|
|
}
|
|
|
|
if(scalar %sessions) {
|
|
print "Sessions:", join("\n- ", '', @sessions_list), "\n";
|
|
}
|
|
|
|
exit $ret;
|
|
}
|
|
|
|
if ($opt_b and scalar %sessions) {
|
|
for my $sess (@sessions_list) {
|
|
print "NEEDRESTART-SESS: $sess\n";
|
|
}
|
|
}
|