#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

# nagios: -epn

package main;

# check_updates is a Nagios plugin to check if RedHat or Fedora system
# is up-to-date
#
# See  the INSTALL file for installation instructions
#
# Copyright (c) 2007-2016, Matteo Corti
#
# This module is free software; you can redistribute it and/or modify it
# under the terms of GNU general public license (gpl) version 3,
# or (at your option) any later version.
# See the COPYING file for details.

use 5.00800;

use strict;
use warnings;

no warnings 'once';    ## no critic (TestingAndDebugging::ProhibitNoWarnings)

our $VERSION = '1.6.18';

use Carp;
use English qw(-no_match_vars);
use POSIX qw(uname);
use Readonly;

my $plugin_module = load_module( 'Monitoring::Plugin', 'Nagios::Plugin' );
my $plugin_threshold_module =
  load_module( 'Monitoring::Plugin::Threshold', 'Nagios::Plugin::Threshold' );
my $plugin_getopt_module =
  load_module( 'Monitoring::Plugin::Getopt', 'Nagios::Plugin::Getopt' );

# Check which version of the monitoring plugins is available

sub load_module {

    my @names = @_;
    my $loaded_module;

    for my $name (@names) {

        my $file = $name;

        # requires need either a bare word or a file name
        $file =~ s{::}{/}gsxm;
        $file .= '.pm';

        eval {    ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
            require $file;
            $name->import();
        };
        if ( !$EVAL_ERROR ) {
            $loaded_module = $name;
            last;
        }
    }

    if ( !$loaded_module ) {
        #<<<
        print 'CHECK_UPDATES: plugin not found: ' . join( ', ', @names ) . "\n";  ## no critic (RequireCheckedSyscall)
        #>>>

        exit 2;
    }

    return $loaded_module;

}

Readonly our $EXIT_UNKNOWN                 => 3;
Readonly our $YUM_RETURN_ERROR             => 1;
Readonly our $YUM_RETURN_UPDATES_AVAILABLE => 100;
Readonly our $BITS_PER_BYTE                => 8;

# IMPORTANT: Nagios plugins could be executed using embedded perl in this case
#            the main routine would be executed as a subroutine and all the
#            declared subroutines would therefore be inner subroutines
#            This will cause all the global lexical variables not to stay shared
#            in the subroutines!
#
# All variables are therefore declared as package variables...
#
## no critic (ProhibitPackageVars)
use vars qw(
  $bootcheck
  $exit_message
  $help
  $options
  $plugin
  $security_plugin
  $status
  $threshold
  $wrong_kernel
  $yum_executable
  @status_lines
);
## use critic

##############################################################################
# Usage     : exit_with_error( $status, $message)
# Purpose   : if a plugin object is available exits via ->nagios_exit
#             otherwise prints to the shell and exit normally
# Returns   : n/a
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub exit_with_error {

    my $status  = shift;
    my $message = shift;

    if ($plugin) {
        $plugin->nagios_exit( $status, $message );
    }
    else {
        #<<<
        print "Error: $message"; ## no critic (RequireCheckedSyscalls)
        #>>>
        exit $status;
    }

    return;

}

##############################################################################
# Usage     : get_path('program_name');
# Purpose   : retrieves the path of an executable file using the
#             'which' utility
# Returns   : the path of the program (if found)
# Arguments : the program name
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub get_path {

    my $prog = shift;
    my $path;

    my $which_command = "which $prog";
    my $which_output;

    #<<<
    open $which_output, q{-|}, "$which_command 2>&1"
      or $plugin->nagios_exit( $plugin->UNKNOWN,  "Cannot execute $which_command: $OS_ERROR" );
    while (<$which_output>) {
        chomp;
        if ( !/^which:/mxs ) {
            $path = $_;
        }
    }
    if (  !( close $which_output )
        && ( $OS_ERROR != 0 ) )
    {

        # close to a piped open return false if the command with non-zero
        # status. In this case $! is set to 0
        $plugin->nagios_exit( $plugin->UNKNOWN,
            "Error while closing pipe to $which_command: $OS_ERROR" );
    }
    #>>>
    return $path;

}

# the script is declared as a package so that it can be unit tested
# but it should not be used as a module
if ( !caller ) {

    # set the yum executable (the value can be overridden in the
    # tests to a mock
    $yum_executable = get_path('dnf');

    if ( !defined $yum_executable ) {
        $yum_executable = get_path('yum');
    }

    run();
}

##############################################################################
# subroutines

##############################################################################
# Usage     : whoami()
# Purpose   : retrieve the user runnging the process
# Returns   : username
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub whoami {
    my $output;
    my $pid = open $output, q{-|},
      'whoami'
      or exit_with_error( $plugin_module->UNKNOWN,
        "Cannot determine the user: $OS_ERROR" );
    while (<$output>) {
        chomp;
        return $_;
    }
    if ( !( close $output ) && ( $OS_ERROR != 0 ) ) {

        # close to a piped open return false if the command with non-zero
        # status. In this case $! is set to 0
        exit_with_error( $plugin_module->UNKNOWN,
            "Error while closing pipe to whoami: $OS_ERROR\n" );

    }

    exit_with_error( $plugin_module->UNKNOWN, 'Cannot determine the user' );
    return;
}

##############################################################################
# Usage     : verbose("some message string", $optional_verbosity_level);
# Purpose   : write a message if the verbosity level is high enough
# Returns   : n/a
# Arguments : message : message string
#             level   : options verbosity level
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub verbose {

    # arguments
    my $message = shift;
    my $level   = shift;

    if ( !defined $message ) {
        exit_with_error( $plugin_module->UNKNOWN,
            q{Internal error: not enough parameters for 'verbose'} );
    }

    if ( !defined $level ) {
        $level = 0;
    }

    # we check if options is defined as the it could be undefined if
    # running from a unit test
    if ( defined $options && $level < $options->verbose ) {
        #<<<
        print $message; ## no critic (RequireCheckedSyscalls)
        #>>>
    }

    return;

}

##############################################################################
# Usage     : versioncmp
# Purpose   : Sort revision-like numbers
# Returns   : -1, 0, or 1 depending on whether the left argument is less than,
#             equal to, or greater than the right argument.
# Arguments : Globally defined $a and $b as the versions to compare
# Throws    : n/a
# Comments  : Included (and adapted) from Sort::Version  to remove the
#             dependency on Sort::Version which is not available per default
#             on RH systems
# See also  : Sort::Version on CPAN
sub versioncmp {

    my @A = ( $a =~ /([-.]|\d+|[^-.\d]+)/gxms );
    my @B = ( $b =~ /([-.]|\d+|[^-.\d]+)/gxms );

    my ( $A, $B );
    while ( @A and @B ) {

        $A = shift @A;
        $B = shift @B;

        ## no critic (ProhibitCascadingIfElse)
        if ( $A eq q{-} and $B eq q{-} ) {
            next;
        }
        elsif ( $A eq q{-} ) {
            return -1;    ## no critic (ProhibitMagicNumbers)
        }
        elsif ( $B eq q{-} ) {
            return 1;
        }
        elsif ( $A eq q{.} and $B eq q{.} ) {
            next;
        }
        elsif ( $A eq q{.} ) {
            return -1;    ## no critic (ProhibitMagicNumbers)
        }
        elsif ( $B eq q{.} ) {
            return 1;
        }
        elsif ( $A =~ /^\d+$/xms and $B =~ /^\d+$/xms ) {
            if ( $A =~ /^0/xms || $B =~ /^0/xms ) {
                return $A cmp $B if $A cmp $B;
            }
            else {
                return $A <=> $B if $A <=> $B;
            }
        }
        else {
            $A = uc $A;
            $B = uc $B;
            return $A cmp $B if $A cmp $B;
        }
        ## use critic (ProhibitCascadingIfElse)

    }
    return @A <=> @B;
}

##############################################################################
# Usage     : $kernel_version = clean_kernel_version( $kernel_version )
# Purpose   : removes everything but the version number from the kernel
#             version (e.g., PAE, xen, ...)
# Returns   : kernel version
# Arguments : kernel_version : output of uname
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub clean_kernel_version {

    my $kernel = shift;

    # remove PAE

    $kernel =~ s/[.][\w]*PAE//imxs;
    $kernel =~ s/PAE-//imxs;

    # remove core
    $kernel =~ s/core-//imxs;

    # remove Xen
    $kernel =~ s/xen-//imxs;

    # remove openvz
    $kernel =~ s/o?vzkernel-//imxs;

    # remove architecture

    $kernel =~ s/[.](i[3-6]86|ppc|x86_64)$//mxs;

    # remove smp

    $kernel =~ s/smp$//mxs;
    $kernel =~ s/smp-//imxs;

    # remove RHEL, CentOS, Scientific Linux and Fedora flavours

    $kernel =~ s/[.]el\d+.*//imxs;
    $kernel =~ s/[.]fc\d+.*//imxs;

    # remove UEK
    $kernel =~ s/uek-//imxs;

    # remove ml-aufs, lt-aufs
    $kernel =~ s/ml-aufs-//imxs;
    $kernel =~ s/lt-aufs-//imxs;

    return $kernel;

}

##############################################################################
# Usage     : check_running_kernel( );
# Purpose   : checks if the loaded kernel is the latest available
# Returns   : n/a
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub check_running_kernel {

    # return if running in openvz client: /proc/vz is always present but
    # /proc/bc only exists on node, but not inside container.
    if ( -d '/proc/vz' && !( -d '/proc/bc' ) ) {
        return;
    }

    if ( !$bootcheck ) {
        return;
    }

    my $package = 'kernel';

    ################################################################################
    # Running

    my ( $sysname, $nodename, $release, $version, $machine ) = POSIX::uname();

    $release = clean_kernel_version($release);

    verbose "running a Linux kernel: $release\n";

    ################################################################################
    # Installed

    my @versions;

    for my $rpm (
        (
            "$package",         "$package-smp",
            "$package-PAE",     "$package-xen",
            "$package-uek",     'ovzkernel',
            'vzkernel',         "$package-PAE-core",
            "$package-ml-aufs", "$package-lt-aufs",
            "$package-core",
        )
      )

    {

        my $output;

        #<<<
        my $pid = open $output, q{-|}, "rpm -q $rpm" ## no critic (RequireBriefOpen)
          or exit_with_error( $plugin_module->UNKNOWN,
            "Cannot list installed kernels: $OS_ERROR" );
        #>>>
        # there could be multiple versions of the same package installed
        my @rpm_versions;
        while (<$output>) {
            chomp;
            push @rpm_versions, $_;
        }

        if ( !( close $output ) && ( $OS_ERROR != 0 ) ) {

            # close to a piped open return false if the command with non-zero
            # status. In this case $! is set to 0
            exit_with_error( $plugin_module->UNKNOWN,
                "Error while closing pipe to rpm: $OS_ERROR\n" );

        }

        if ( $CHILD_ERROR == 0 ) {

            # rpm exits with 0 only it the RPM exists

            push @versions, @rpm_versions;

        }

    }

    for (@versions) {

        # strip package name
        s/^$package-//mxs;
        $_ = clean_kernel_version($_);

    }

    @versions = sort versioncmp @versions;

    my $installed = $versions[-1];

    if ( !$installed ) {
        $plugin->nagios_exit( $plugin->UNKNOWN,
            'Unable to detect installed kernel' );
    }

    verbose "kernel: running = $release, installed = $installed\n";

    if ( $installed ne $release ) {

        my $error =
"your machine is running kernel $release but a newer version ($installed) is installed: you should reboot";

        if ($exit_message) {
            $exit_message .= $error;
        }
        else {
            $exit_message = $error;
        }

        $wrong_kernel = 1;

    }

    return;

}

##############################################################################
# Usage     : run_yum();
# Purpose   : runs yum check-update with the supplied command line argumens
# Returns   : the list of updates
# Arguments : string with the command line arguments
#           : (optional) yum command
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub run_yum {

    my $arguments   = shift;
    my $yum_command = shift;

    # per default check updates
    if ( !defined $yum_command ) {
        $yum_command = 'check-update';
    }

    my $blank_line;
    my $OUTPUT_HANDLER;
    my @updates;

    my $command = "$yum_executable $arguments $yum_command";

    verbose qq{Running "$command"\n}, 1;

    #<<<
    my $pid = open $OUTPUT_HANDLER, q{-|}, $command ## no critic (RequireBriefOpen)
      or exit_with_error( $plugin_module->UNKNOWN, "Cannot list updates: $OS_ERROR" );
    #>>>
    while (<$OUTPUT_HANDLER>) {

        my $line = $_;

        verbose $line, 1;

        chomp $line;

        if ( !$line ) {
            $blank_line = 1;
            next;
        }

        if ($blank_line) {

            # some lines are wrapped and result and the second part
            # is erroneously interpreted as a new update
            if ( $line =~ m/^[ ]/mxs ) {
                next;
            }

            if ( $line =~ /^Obsoleting/imxs ) {
                next;
            }

            $line =~ s{[ ].*}{}mxs;

            push @updates, $line;
        }

    }

    if ( !( close $OUTPUT_HANDLER ) && ( $OS_ERROR != 0 ) ) {

        # close to a piped open return false if the command with non-zero
        # status. In this case $! is set to 0
        exit_with_error( $plugin_module->UNKNOWN,
            "Error while closing pipe to rpm: $OS_ERROR\n" );

    }

    if ( ( $CHILD_ERROR >> $BITS_PER_BYTE ) == $YUM_RETURN_UPDATES_AVAILABLE ) {
        if ($security_plugin) {
            verbose "Security updates available\n";
        }
        else {
            verbose "Updates available\n";
        }
    }
    elsif ( ( $CHILD_ERROR >> $BITS_PER_BYTE ) == $YUM_RETURN_ERROR ) {
        exit_with_error( $plugin_module->UNKNOWN,
            "Error while executing '$command'\n" );
    }

    return @updates;

}

##############################################################################
# Usage     : check_security_option
# Purpose   : checks if you supports --security (in older versions required
#             a plugin)
# Returns   : 1 if --security is supported, undef otherwise
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub check_security_option {

    my $OUTPUT_HANDLER;

    my $pid = open $OUTPUT_HANDLER, q{-|},
      "$yum_executable --help | grep -q -- --security"
      or exit_with_error( $plugin_module->UNKNOWN,
        "Cannot check for security option: $OS_ERROR" );

    if ( !( close $OUTPUT_HANDLER ) && ( $OS_ERROR != 0 ) ) {

        # close to a piped open return false if the command with non-zero
        # status. In this case $! is set to 0
        exit_with_error( $plugin_module->UNKNOWN,
            "Error while closing pipe to rpm: $OS_ERROR\n" );

    }

    if ( $CHILD_ERROR == 0 ) {
        verbose "Security plugin installed\n";
        $security_plugin = 1;
    }

    return;

}

##############################################################################
# Usage     : check_yum();
# Purpose   : checks a yum based system for updates
# Returns   : n/a
# Arguments : string with the command line arguments
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
## no critic (ProhibitExcessComplexity)
sub check_yum {

    my $arguments = shift;

    my $message;

    check_security_option();

    if ( $options->get('clean') ) {
        run_yum( '-q', 'clean all' );
    }

    my @outdated = run_yum($arguments);
    my @security_updates;

    $plugin->add_perfdata(
        label     => 'total_updates',
        value     => scalar @outdated,
        uom       => q{},
        threshold => $threshold,
    );

    if ( @outdated > 0 ) {

        if ( !$security_plugin ) {

            if ( $options->get('security-only') ) {
                verbose
"up2date does not distinguish security updates ignoring --security-only option\n";
            }

            # every update is critical since it could be a security problem

            $status = $plugin_module->CRITICAL;
            verbose "dnf/yum reports a non-up-to-date system\n";

            $message = ( scalar @outdated ) . ' update';
            if ( @outdated > 1 ) {
                $message = $message . q{s};
            }
            $message = $message . ' available';

            @status_lines = @outdated;

        }
        else {

            if ( @outdated >= $options->get('warning') ) {
                $status = $plugin_module->WARNING;
            }
            if ( @outdated >= $options->get('critical') ) {
                $status = $plugin_module->CRITICAL;
            }

            if ( $options->get('security-only') ) {

                # ignore any other update
                $status = $plugin_module->OK;
            }

            @security_updates = run_yum( '--security ' . $arguments );

            if ( @security_updates > 0 ) {

                $status = $plugin_module->CRITICAL;

                $message = ( scalar @security_updates ) . ' security update';
                if ( @security_updates > 1 ) {
                    $message = $message . q{s};
                }

                # compute the array difference
                my %count;

                for my $element (@security_updates) {
                    push @status_lines, "$element (security)";
                    $count{$element}++;
                }

                for my $element (@outdated) {
                    $count{$element}++;
                }

                my @difference;
                for my $element (@outdated) {
                    if ( $count{$element} == 1 ) {
                        push @difference, $element;
                    }
                }

                @outdated = @difference;

            }

            if ( @outdated > 0 ) {

                if ($message) {
                    $message .= ' and ';
                }

                $message .= ( scalar @outdated ) . ' non-critical update';
                if ( @outdated > 1 ) {
                    $message = $message . q{s};
                }

                push @status_lines, @outdated;

            }

            if ($message) {
                $message .= ' available';
            }

        }

    }
    else {
        $status  = $plugin_module->OK;
        $message = 'no updates available';
    }

    if ($security_plugin) {
        $plugin->add_perfdata(
            label     => 'security_updates',
            value     => scalar @security_updates,
            uom       => q{},
            threshold => $threshold,
        );
    }

    if ($exit_message) {
        $exit_message .= q{, } . $message;
    }
    else {
        $exit_message = $message;
    }

    return;

}
## use critic (ProhibitExcessComplexity)

##############################################################################
# Usage     : check_up2date();
# Purpose   : checks an up2date based system for updates
# Returns   : n/a
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub check_up2date {

    if ( $options->get('clean') ) {
        verbose 'clean not supported with up2date';
    }

    # parsing the output of up2date -l

    if ( whoami() ne 'root' ) {
        exit_with_error( $plugin_module->CRITICAL,
            q{must be root to execute 'up2date -l': use sudo} );
    }

    my $output;

    my $command =
q{/usr/sbin/up2date -lf | /bin/grep -A 64 -- '----------------------------------------------------------' | /bin/grep '[[:alpha:]]'};

    my $pid = open $output, q{-|}, $command    ## no critic (RequireBriefOpen)
      or exit_with_error( $plugin_module->UNKNOWN,
        "Cannot list updates: $OS_ERROR" );

    while (<$output>) {
        chomp;
        my $line = $_;
        $line =~ s/[ ].*//mxs;
        push @status_lines, $line;
    }

    if ( !( close $output ) && ( $OS_ERROR != 0 ) ) {

        # close to a piped open return false if the command with non-zero
        # status. In this case $! is set to 0
        exit_with_error( $plugin_module->UNKNOWN,
            "Error while closing pipe to dnf/yum (listing updates): $OS_ERROR\n"
        );

    }

    my $message;

    if ( @status_lines > 0 ) {

        $message = ( scalar @status_lines ) . ' update';

        if ( @status_lines >= $options->get('warning') ) {
            $status = $plugin_module->WARNING;
        }
        if ( @status_lines >= $options->get('critical') ) {
            $status = $plugin_module->CRITICAL;
        }

        if ( @status_lines > 1 ) {
            $message = $message . q{s};
        }
        $message = $message . ' available';
        $plugin->add_perfdata(
            label     => 'updates',
            value     => scalar @status_lines,
            uom       => q{},
            threshold => $threshold,
        );

    }
    else {

        $message = 'no updates available';

        $plugin->add_perfdata(
            label     => 'updates',
            value     => 0,
            uom       => q{},
            threshold => $threshold,
        );

    }

    if ($exit_message) {
        $exit_message .= " $message";
    }
    else {
        $exit_message = $message;
    }

    return;

}

##############################################################################
# Usage     : my $system = get_os_name_and_version();
# Purpose   : returns the name of the system by parsing $VERSION_FILE
# Returns   : name of the system
# Arguments : n/a
# Throws    : CRITICAL if not able to read $VERSION_FILE
# Comments  : n/a
# See also  : n/a
sub get_os_name_and_version {

    my $filename = shift;
    if ( !$filename ) {

        if ( -r '/etc/system-release' ) {
            $filename = '/etc/system-release';
        }
        elsif ( -r '/etc/redhat-release' ) {
            $filename = '/etc/redhat-release';
        }
        else {
            exit_with_error( $plugin_module->UNKNOWN,
                'Cannot detect Linux distribution' );
        }

    }

    my $header;

    if ( -r $filename ) {

        my $TMP;

        open $TMP, q{<},
          $filename
          or exit_with_error( $plugin_module->CRITICAL,
            "Error opening $filename: $OS_ERROR" );
        while (<$TMP>) {
            chomp;
            $header = $_;
            last;
        }
        close $TMP
          or exit_with_error( $plugin_module->CRITICAL,
            "Error closing $filename: $OS_ERROR" );

    }
    else {
        exit_with_error( $plugin_module->UNKNOWN,
            "Cannot detect Linux distribution (no $filename file)" );
    }

    return $header;

}

##############################################################################
# Usage     : my $updater = get_updated()
# Purpose   : returns the name of updating system (yum or up2date)
# Returns   : name of updating system or undef if not found
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub get_updater {

    my $name = shift;

    if (   $name =~ /Fedora/mxs
        || $name =~ /CentOS/mxs
        || $name =~ /PUIAS/mxs
        || $name =~ /CloudLinux/mxs
        || $name =~ /Scientific[ ]Linux/mxs
        || $name =~ /Red[ ]Hat.*[ ][567]/mxs
        || $name =~ /Oracle/mxs
        || $name =~ /Amazon/mxs )
    {
        return 'yum';
    }
    elsif ( $name =~ /Red[ ]Hat.*[ ]4/mxs ) {
        return 'up2date';
    }
    return;

}

##############################################################################
# Usage     : run();
# Purpose   : main method
# Returns   : n/a
# Arguments : n/a
# Throws    : n/a
# Comments  : n/a
# See also  : n/a
sub run {

    $status = $plugin_module->OK;

    ##############################################################################
    # main
    #

    ################
    # initialization
    $help         = q{};
    $bootcheck    = 1;
    $wrong_kernel = 0;
    $plugin       = $plugin_module->new( shortname => 'CHECK_UPDATES' );

    ########################
    # Command line arguments

    $options = $plugin_getopt_module->new(
        usage   => 'Usage: %s [OPTIONS]',
        version => $VERSION,
        url     => 'https://github.com/matteocorti/check_updates',
        blurb   => 'Checks if RedHat or Fedora system is up-to-date',
    );

    $options->arg(
        spec => 'boot-check',
        help =>
          'Check if the machine was booted with the newest kernel (default)',
    );

    $options->arg(
        spec => 'boot-check-warning',
        help => 'Like --boot-check but state is warning instead of critical',
    );

    $options->arg(
        spec => 'no-boot-check',
        help => 'do not complain if the machine was booted with an old kernel',
    );

    $options->arg(
        spec => 'clean',
        help => 'Cleans YUM/DNF caches',
    );

    $options->arg(
        spec => 'warning|w=i',
        help =>
'Exit with WARNING status if more than INTEGER non-security updates are available',
        default => 0
    );

    $options->arg(
        spec => 'critical|c=i',
        help =>
'Exit with CRITICAL status if more than INTEGER non-security updates are available',
        default => 0
    );

    $options->arg(
        spec => 'security-only',
        help => 'Ignores non-security updates',
    );

    $options->arg(
        spec => 'yum-arguments|a=s',
        help => 'specific Yum arguments as STRING',

    );

    $options->getopts();

    ###############
    # Sanity checks

    if ( $options->get('warning') > $options->get('critical') ) {
        exit_with_error( $plugin_module->UNKNOWN,
                q{'critical' (}
              . $options->get('critical')
              . q{) must not be lower than 'warning' (}
              . $options->get('warning')
              . q{)} );
    }

    $threshold = $plugin_threshold_module->set_thresholds(
        warning  => $options->get('warning'),
        critical => $options->get('critical'),
    );

    # check bootcheck consistency
    if ( $options->get('boot-check') && $options->get('no-boot-check') ) {
        exit_with_error( $plugin_module->CRITICAL,
            'Error --boot-check and --no-boot-check specified at the same time'
        );
    }
    if ( $options->get('boot-check-warning') && $options->get('no-boot-check') )
    {
        exit_with_error( $plugin_module->CRITICAL,
'Error --boot-check-warning and --no-boot-check specified at the same time'
        );
    }

    if ( $options->get('no-boot-check') ) {
        $bootcheck = 0;
    }

    if ( $options->get('boot-check-warning') ) {
        $bootcheck = 2;
    }

    if ( !defined $yum_executable ) {
        exit_with_error( $plugin_module->UNKNOWN, "Cannot find DNF or YUM\n" );
    }

    #########
    # Arguments

    my $arguments = q{};
    if ( $options->get('yum-arguments') ) {
        $arguments = $options->get('yum-arguments');
    }

    #########
    # Timeout

    alarm $options->timeout;

    verbose "Checking a $OSNAME system\n";

    if ( $OSNAME eq 'linux' ) {

        my $name = get_os_name_and_version();
        verbose "Running on $name\n";

        my $updater = get_updater($name);

        if ( $updater eq 'yum' ) {
            verbose "Using Yum\n";
            check_running_kernel();
            check_yum($arguments);
        }
        elsif ( $updater eq 'up2date' ) {
            verbose "Using up2date\n";

            if ( $options->get('security-only') ) {
                verbose
"up2date does not distinguish security updates ignoring --security-only option\n";
            }

            check_running_kernel();
            check_up2date();
        }
        else {
            exit_with_error( $plugin_module->UNKNOWN,
                'unknown Linux distribution' );
        }

        if ( $bootcheck == 2 && $wrong_kernel ) {
            $status = $plugin_module->WARNING;
        }
        elsif ( $bootcheck && $wrong_kernel ) {
            $status = $plugin_module->CRITICAL;
        }

        # Monitoring::Plugin does not support the addition Nagios 3 status lines
        # -> we do it manually

        eval {    ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
            ## no critic (Modules::RequireBarewordIncludes)
            require 'Monitoring/Plugin.pm';
            ## use critic (Modules::RequireBarewordIncludes)
            Monitoring::Plugin->import();
        };
        if ( !$EVAL_ERROR ) {
            #<<<
            print 'CHECK_UPDATES '  ## no critic (RequireCheckedSyscalls)
                . $Monitoring::Plugin::STATUS_TEXT{$status}
                    . " - $exit_message |";
            #>>>
        }
        else {
            #<<<
            print 'CHECK_UPDATES '  ## no critic (RequireCheckedSyscalls)
                . $Nagios::Plugin::STATUS_TEXT{$status}
                    . " - $exit_message |";
            #>>>
        }

        for my $pdata ( @{ $plugin->perfdata } ) {
            #<<<
            print q{ } . $pdata->perfoutput; ## no critic (RequireCheckedSyscalls)
            #>>>
        }

        #<<<
        print "\n"; ## no critic (RequireCheckedSyscalls)
        #>>>
        for my $package (@status_lines) {
            #<<<
            print "$package\n"; ## no critic (RequireCheckedSyscalls)
            #>>>
        }

        exit $status;

    }
    else {
        exit_with_error( $plugin_module->UNKNOWN,
            'Cannot detect Linux system' );
    }

    return;

}

1;
