Linux lhjmq-records 5.15.0-118-generic #128-Ubuntu SMP Fri Jul 5 09:28:59 UTC 2024 x86_64
Your IP : 3.142.55.138
Current Path : /sbin/ |
| Current File : //sbin/needrestart |
#!/usr/bin/perl
# nagios: -epn
# needrestart - Restart daemons after library updates.
#
# Authors:
# Thomas Liske <thomas@fiasko-nw.net>
#
# Copyright Holder:
# 2013 - 2020 (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 (-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
-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 - 2020 (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
#/
}
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;
}
# 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);
# 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($@);
# fallback to stdio on verbose mode
$nrconf{ui} = qq(NeedRestart::UI::stdio) if($nrconf{verbosity} > 1);
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' && exists($ENV{DEBIAN_FRONTEND}) && $ENV{DEBIAN_FRONTEND} eq 'noninteractive');
$opt_r = 'l' if(!$is_tty && $opt_r eq 'i');
# always run in batch mode if we run noninteractive
$opt_b++ if(exists($ENV{DEBIAN_FRONTEND}) && $ENV{DEBIAN_FRONTEND} eq 'noninteractive');
$opt_m = $nrconf{ui_mode} unless(defined($opt_m));
die "ERROR: Unknown UI mode '$opt_m'!\n" unless($opt_m =~ /^(e|a)$/);
$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}, ($is_tty ? $nrconf{ui} : 'NeedRestart::UI::stdio')))."\n";
exit 0;
}
else {
$nrconf{ui} = $opt_u;
}
}
my $ui = ($opt_b ? NeedRestart::UI->new(0) : needrestart_ui($nrconf{verbosity}, ($is_tty ? $nrconf{ui} : 'NeedRestart::UI::stdio')));
die "Error: no UI class available!\n" unless(defined($ui));
# 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, $opt_t));
# 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
if(open(HCGROUP, qq(/proc/$pid/cgroup))) {
my ($rc) = map {
chomp;
my ($id, $type, $value) = split(/:/);
if($type ne q(name=systemd)) {
();
}
else {
if($value =~ 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($value =~ 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($value =~ 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 '$value'\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 '$value'\n" if($nrconf{verbosity} > 1);
push(@guests, __x("'Unkown VM' with pid {pid}", pid=>$pid) );
next;
}
elsif($value =~ m@/([^/]+\.service)$@) {
($1);
}
else {
print STDERR "$LOGPREF #$pid unexpected cgroup '$value'\n" if($nrconf{verbosity} > 1);
();
}
}
} <HCGROUP>;
close(HCGROUP);
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} .= " (!!)";
}
}
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};
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');
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...
my $restart = !$nrconf{defno};
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";
}
}
|