]> git.uio.no Git - check_openmanage.git/blob - check_openmanage
* version 3.7.1-beta1
[check_openmanage.git] / check_openmanage
1 #!/usr/bin/perl
2 #
3 # Nagios plugin
4 #
5 # Monitor Dell server hardware status using Dell OpenManage Server
6 # Administrator, either locally via NRPE, or remotely via SNMP.
7 #
8 # $Id$
9 #
10 # Copyright (C) 2008-2011 Trond H. Amundsen
11 #
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful, but
18 # WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 # General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 require 5.006;  # Perl v5.6.0 or newer is required
27 use strict;
28 use warnings;
29 use POSIX qw(isatty ceil);
30 use Getopt::Long qw(:config no_ignore_case);
31
32 # Global (package) variables used throughout the code
33 use vars qw( $NAME $VERSION $AUTHOR $CONTACT $E_OK $E_WARNING $E_CRITICAL
34              $E_UNKNOWN $FW_LOCK $USAGE $HELP $LICENSE
35              $snmp_session $snmp_error $omreport $globalstatus $global
36              $linebreak $omopt_chassis $omopt_system $blade
37              $exit_code $snmp
38              %check %opt %reverse_exitcode %status2nagios
39              %snmp_status %snmp_probestatus %probestatus2nagios %sysinfo
40              %blacklist %nagios_alert_count %count %snmp_enclosure %snmp_controller
41              @perl_warnings @controllers @enclosures @perfdata
42              @report_storage @report_chassis @report_other
43           );
44
45 #---------------------------------------------------------------------
46 # Initialization and global variables
47 #---------------------------------------------------------------------
48
49 # Collect perl warnings in an array
50 $SIG{__WARN__} = sub { push @perl_warnings, [@_]; };
51
52 # Version and similar info
53 $NAME    = 'check_openmanage';
54 $VERSION = '3.7.1-beta1';
55 $AUTHOR  = 'Trond H. Amundsen';
56 $CONTACT = 't.h.amundsen@usit.uio.no';
57
58 # Exit codes
59 $E_OK       = 0;
60 $E_WARNING  = 1;
61 $E_CRITICAL = 2;
62 $E_UNKNOWN  = 3;
63
64 # Firmware update lock file [FIXME: location on Windows?]
65 $FW_LOCK = '/var/lock/.spsetup';  # default on Linux
66
67 # Usage text
68 $USAGE = <<"END_USAGE";
69 Usage: $NAME [OPTION]...
70 END_USAGE
71
72 # Help text
73 $HELP = <<'END_HELP';
74
75 GENERAL OPTIONS:
76
77    -f, --config         Specify configuration file
78    -p, --perfdata       Output performance data [default=no]
79    -t, --timeout        Plugin timeout in seconds [default=30]
80    -c, --critical       Custom temperature critical limits
81    -w, --warning        Custom temperature warning limits
82    -F, --fahrenheit     Use Fahrenheit as temperature unit
83    -d, --debug          Debug output, reports everything
84    -h, --help           Display this help text
85    -V, --version        Display version info
86
87 SNMP OPTIONS:
88
89    -H, --hostname       Hostname or IP (required for SNMP)
90    -C, --community      SNMP community string [default=public]
91    -P, --protocol       SNMP protocol version [default=2]
92    --port               SNMP port number [default=161]
93    -6, --ipv6           Use IPv6 instead of IPv4 [default=no]
94    --tcp                Use TCP instead of UDP [default=no]
95
96 OUTPUT OPTIONS:
97
98    -i, --info           Prefix any alerts with the service tag
99    -e, --extinfo        Append system info to alerts
100    -s, --state          Prefix alerts with alert state
101    -S, --short-state    Prefix alerts with alert state abbreviated
102    -o, --okinfo         Verbosity when check result is OK
103    -B, --show-blacklist Show blacklistings in OK output
104    -I, --htmlinfo       HTML output with clickable links
105
106 CHECK CONTROL AND BLACKLISTING:
107
108    -a, --all            Check everything, even log content
109    -b, --blacklist      Blacklist missing and/or failed components
110    --only               Only check a certain component or alert type
111    --check              Fine-tune which components are checked
112    --no-storage         Don't check storage
113
114 For more information and advanced options, see the manual page or URL:
115   http://folk.uio.no/trondham/software/check_openmanage.html
116 END_HELP
117
118 # Version and license text
119 $LICENSE = <<"END_LICENSE";
120 $NAME $VERSION
121 Copyright (C) 2008-2011 $AUTHOR
122 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
123 This is free software: you are free to change and redistribute it.
124 There is NO WARRANTY, to the extent permitted by law.
125
126 Written by $AUTHOR <$CONTACT>
127 END_LICENSE
128
129 # Options with default values
130 %opt = ( 'blacklist'         => [],       # blacklisting
131          'check'             => [],       # check control
132          'critical'          => [],       # temperature critical limits
133          'warning'           => [],       # temperature warning limits
134          'tempunit'          => 'C',      # temperature unit
135          'fahrenheit'        => 0,        # Use fahrenheit
136          'configfile'        => undef,    # configuration file
137          'timeout'           => 30,       # default timeout is 30 seconds
138          'debug'             => 0,        # debugging / verbose output
139          'help'              => 0,        # display help output
140          'perfdata'          => undef,    # output performance data
141          'legacy_perfdata'   => 0,        # legacy performance data output
142          'info'              => 0,        # display servicetag
143          'extinfo'           => 0,        # display extra info
144          'htmlinfo'          => undef,    # html tags in output
145          'postmsg'           => undef,    # post message
146          'state'             => 0,        # display alert type
147          'short-state'       => 0,        # display alert type (short)
148          'okinfo'            => 0,        # default "ok" output level
149          'show_blacklist'    => 0,        # show blacklisted components
150          'linebreak'         => undef,    # specify linebreak
151          'version'           => 0,        # plugin version info
152          'all'               => 0,        # check everything
153          'only'              => undef,    # only one component
154          'no_storage'        => 0,        # don't check storage
155          'omreport'          => undef,    # omreport path
156          'port'              => 161,      # default SNMP port
157          'hostname'          => undef,    # hostname or IP
158          'community'         => 'public', # SMNP v1 or v2c
159          'protocol'          => 2,        # default SNMP protocol 2c
160          'ipv6'              => 0,        # default is IPv4
161          'tcp'               => 0,        # default is UDP
162          'username'          => undef,    # SMNP v3
163          'authpassword'      => undef,    # SMNP v3
164          'authkey'           => undef,    # SMNP v3
165          'authprotocol'      => undef,    # SMNP v3
166          'privpassword'      => undef,    # SMNP v3
167          'privkey'           => undef,    # SMNP v3
168          'privprotocol'      => undef,    # SMNP v3
169          'use_get_table'     => 0,        # hack for SNMPv3 on Windows with net-snmp
170        );
171
172 # Get options
173 GetOptions('b|blacklist=s'      => \@{ $opt{blacklist} },
174            'check=s'            => \@{ $opt{check} },
175            'c|critical=s'       => \@{ $opt{critical} },
176            'w|warning=s'        => \@{ $opt{warning} },
177            'tempunit=s'         => \$opt{tempunit},
178            'F|fahrenheit'       => \$opt{fahrenheit},
179            'f|configfile=s'     => \$opt{configfile},
180            't|timeout=i'        => \$opt{timeout},
181            'd|debug'            => \$opt{debug},
182            'h|help'             => \$opt{help},
183            'V|version'          => \$opt{version},
184            'p|perfdata:s'       => \$opt{perfdata},
185            'legacy-perfdata'    => \$opt{legacy_perfdata},
186            'i|info'             => \$opt{info},
187            'e|extinfo'          => \$opt{extinfo},
188            'I|htmlinfo:s'       => \$opt{htmlinfo},
189            'postmsg=s'          => \$opt{postmsg},
190            's|state'            => \$opt{state},
191            'S|short-state'      => \$opt{shortstate},
192            'o|ok-info=i'        => \$opt{okinfo},
193            'B|show-blacklist'   => \$opt{show_blacklist},
194            'linebreak=s'        => \$opt{linebreak},
195            'a|all'              => \$opt{all},
196            'only=s'             => \$opt{only},
197            'no-storage'         => \$opt{no_storage},
198            'omreport=s'         => \$opt{omreport},
199            'port=i'             => \$opt{port},
200            'H|hostname=s'       => \$opt{hostname},
201            'C|community=s'      => \$opt{community},
202            'P|protocol=i'       => \$opt{protocol},
203            '6|ipv6'             => \$opt{ipv6},
204            'tcp'                => \$opt{tcp},
205            'U|username=s'       => \$opt{username},
206            'authpassword=s'     => \$opt{authpassword},
207            'authkey=s'          => \$opt{authkey},
208            'authprotocol=s'     => \$opt{authprotocol},
209            'privpassword=s'     => \$opt{privpassword},
210            'privkey=s'          => \$opt{privkey},
211            'privprotocol=s'     => \$opt{privprotocol},
212            'use-get_table'      => \$opt{use_get_table},
213           ) or do { print $USAGE; exit $E_UNKNOWN };
214
215 # If user requested help
216 if ($opt{help}) {
217     print $USAGE, $HELP;
218     exit $E_UNKNOWN;
219 }
220
221 # If user requested version info
222 if ($opt{version}) {
223     print $LICENSE;
224     exit $E_UNKNOWN;
225 }
226
227 # Initialize blacklist
228 %blacklist = ();
229
230 # Check flags, override available with the --check option
231 %check = ( 'storage'     => 1,   # check storage subsystem
232            'memory'      => 1,   # check memory (dimms)
233            'fans'        => 1,   # check fan status
234            'power'       => 1,   # check power supplies
235            'temp'        => 1,   # check temperature
236            'cpu'         => 1,   # check processors
237            'voltage'     => 1,   # check voltage
238            'batteries'   => 1,   # check battery probes
239            'amperage'    => 1,   # check power consumption
240            'intrusion'   => 1,   # check intrusion detection
241            'sdcard'      => 1,   # check removable flash media (SD cards)
242            'alertlog'    => 0,   # check the alert log
243            'esmlog'      => 0,   # check the ESM log (hardware log)
244            'esmhealth'   => 1,   # check the ESM log overall health
245          );
246
247 # Messages
248 @report_storage = ();  # messages with associated nagios level (storage)
249 @report_chassis = ();  # messages with associated nagios level (chassis)
250 @report_other   = ();  # messages with associated nagios level (other)
251
252 # Read config file
253 parse_configfile() if defined $opt{configfile};
254
255 # Setting timeout
256 $SIG{ALRM} = sub {
257     print "PLUGIN TIMEOUT: $NAME timed out after $opt{timeout} seconds\n";
258     exit $E_UNKNOWN;
259 };
260 alarm $opt{timeout};
261
262 # If we're using SNMP
263 $snmp = defined $opt{hostname} ? 1 : 0;
264
265 # SNMP session variables
266 $snmp_session = undef;
267 $snmp_error   = undef;
268
269 # The omreport command
270 $omreport = undef;
271
272 # Default line break
273 $linebreak = isatty(*STDOUT) ? "\n" : '<br/>';
274
275 # Line break from option
276 if (defined $opt{linebreak}) {
277     if ($opt{linebreak} eq 'REG') {
278         $linebreak = "\n";
279     }
280     elsif ($opt{linebreak} eq 'HTML') {
281         $linebreak = '<br/>';
282     }
283     else {
284         $linebreak = $opt{linebreak};
285     }
286 }
287
288 # Exit with status=UNKNOWN if there is firmware upgrade in progress
289 if (!$snmp && -f $FW_LOCK) {
290     print "MONITORING DISABLED - Firmware update in progress ($FW_LOCK exists)\n";
291     exit $E_UNKNOWN;
292 }
293
294 # List of controllers and enclosures
295 @controllers = ();  # controllers
296 @enclosures  = ();  # enclosures
297 %snmp_enclosure   = ();  # enclosures
298
299 # Counters for everything
300 %count
301   = (
302      'pdisk'  => 0, # number of physical disks
303      'vdisk'  => 0, # number of logical drives (virtual disks)
304      'temp'   => 0, # number of temperature probes
305      'volt'   => 0, # number of voltage probes
306      'amp'    => 0, # number of amperage probes
307      'intr'   => 0, # number of intrusion probes
308      'dimm'   => 0, # number of memory modules
309      'mem'    => 0, # total memory
310      'fan'    => 0, # number of fan probes
311      'cpu'    => 0, # number of CPUs
312      'bat'    => 0, # number of batteries
313      'power'  => 0, # number of power supplies
314      'sd'     => 0, # number of SD cards
315      'esm'    => {
316                   'Critical'     => 0, # critical entries in ESM log
317                   'Non-Critical' => 0, # warning entries in ESM log
318                   'Ok'           => 0, # ok entries in ESM log
319                  },
320      'alert'  => {
321                   'Critical'     => 0, # critical entries in alert log
322                   'Non-Critical' => 0, # warning entries in alert log
323                   'Ok'           => 0, # ok entries in alert log
324                  },
325     );
326
327 # Performance data
328 @perfdata = ();
329
330 # Global health status
331 $global         = 1;      # default is to check global status
332 $globalstatus   = $E_OK;  # default global health status is "OK"
333
334 # Nagios error levels reversed
335 %reverse_exitcode
336   = (
337      $E_OK       => 'OK',
338      $E_WARNING  => 'WARNING',
339      $E_CRITICAL => 'CRITICAL',
340      $E_UNKNOWN  => 'UNKNOWN',
341     );
342
343 # OpenManage (omreport) and SNMP error levels
344 %status2nagios
345   = (
346      'Unknown'         => $E_CRITICAL,
347      'Critical'        => $E_CRITICAL,
348      'Non-Critical'    => $E_WARNING,
349      'Ok'              => $E_OK,
350      'Non-Recoverable' => $E_CRITICAL,
351      'Other'           => $E_CRITICAL,
352     );
353
354 # Status via SNMP
355 %snmp_status
356   = (
357      1 => 'Other',
358      2 => 'Unknown',
359      3 => 'Ok',
360      4 => 'Non-Critical',
361      5 => 'Critical',
362      6 => 'Non-Recoverable',
363     );
364
365 # Probe Status via SNMP
366 %snmp_probestatus
367   = (
368      1  => 'Other',               # probe status is not one of the following:
369      2  => 'Unknown',             # probe status is unknown (not known or monitored)
370      3  => 'Ok',                  # probe is reporting a value within the thresholds
371      4  => 'nonCriticalUpper',    # probe has crossed upper noncritical threshold
372      5  => 'criticalUpper',       # probe has crossed upper critical threshold
373      6  => 'nonRecoverableUpper', # probe has crossed upper non-recoverable threshold
374      7  => 'nonCriticalLower',    # probe has crossed lower noncritical threshold
375      8  => 'criticalLower',       # probe has crossed lower critical threshold
376      9  => 'nonRecoverableLower', # probe has crossed lower non-recoverable threshold
377      10 => 'failed',              # probe is not functional
378     );
379
380 # Probe status translated to Nagios alarm levels
381 %probestatus2nagios
382   = (
383      'Other'               => $E_CRITICAL,
384      'Unknown'             => $E_CRITICAL,
385      'Ok'                  => $E_OK,
386      'nonCriticalUpper'    => $E_WARNING,
387      'criticalUpper'       => $E_CRITICAL,
388      'nonRecoverableUpper' => $E_CRITICAL,
389      'nonCriticalLower'    => $E_WARNING,
390      'criticalLower'       => $E_CRITICAL,
391      'nonRecoverableLower' => $E_CRITICAL,
392      'failed'              => $E_CRITICAL,
393     );
394
395 # System information gathered
396 %sysinfo
397   = (
398      'bios'     => 'N/A',  # BIOS version
399      'biosdate' => 'N/A',  # BIOS release date
400      'serial'   => 'N/A',  # serial number (service tag)
401      'model'    => 'N/A',  # system model
402      'rev'      => q{},    # system revision
403      'osname'   => 'N/A',  # OS name
404      'osver'    => 'N/A',  # OS version
405      'om'       => 'N/A',  # OMSA version
406      'bmc'      => 0,      # HAS baseboard management controller (BMC)
407      'rac'      => 0,      # HAS remote access controller (RAC)
408      'rac_name' => 'N/A',  # remote access controller (RAC)
409      'bmc_fw'   => 'N/A',  # BMC firmware
410      'rac_fw'   => 'N/A',  # RAC firmware
411     );
412
413 # Adjust which checks to perform
414 adjust_checks() if defined $opt{check};
415
416 # Blacklisted components
417 set_blacklist($opt{blacklist}) if defined $opt{blacklist};
418
419 # If blacklisting is in effect, don't check global health status
420 if (scalar keys %blacklist > 0) {
421     $global = 0;
422 }
423
424 # Take into account new hardware and blades
425 $omopt_chassis = 'chassis';  # default "chassis" option to omreport
426 $omopt_system  = 'system';   # default "system" option to omreport
427 $blade         = 0;          # if this is a blade system
428
429 # Some initializations and checking before we begin
430 if ($snmp) {
431     snmp_initialize();    # initialize SNMP
432     snmp_check();         # check that SNMP works
433     snmp_detect_blade();  # detect blade via SNMP
434 }
435 else {
436     # Find the omreport binary
437     find_omreport();
438     # Check help output from omreport, see which options are available.
439     # Also detecting blade via omreport.
440     check_omreport_options();
441 }
442
443 # Temperature unit
444 if ($opt{fahrenheit}) {
445     $opt{tempunit} = 'F';
446 }
447
448 # Check tempunit syntax
449 if ($opt{tempunit} !~ m{\A C|F|K|R \z}xms) {
450     print "ERROR: Unknown temperature unit '$opt{tempunit}'\n";
451     exit $E_UNKNOWN;
452 }
453
454 #---------------------------------------------------------------------
455 # Helper functions
456 #---------------------------------------------------------------------
457
458 # Make a regex from a glob pattern. Shamelessly stolen from Perl
459 # Cookbook chapter 6.9
460 sub glob2regex {
461     my $globstr = shift;
462     my %patmap
463       = ( '*' => '.*',
464           '?' => '.',
465           '[' => '[',
466           ']' => ']',
467         );
468     $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
469     return '\A' . $globstr . '\z';
470 }
471
472 #
473 # Read config file
474 #
475 sub parse_configfile {
476     our $tiny = undef;
477
478     # Regexp for boolean values
479     our $off = qr{\A (0|off|false) \s* \z}ixms;
480     our $on  = qr{\A (1|on|true) \s* \z}ixms;
481
482     # Mapping between command line options and the corresponding
483     # config file options
484     our %opt2config
485       = ( 'info'            => 'output_servicetag',
486           'extinfo'         => 'output_sysinfo',
487           'postmsg'         => 'output_post_message',
488           'state'           => 'output_servicestate',
489           'shortstate'      => 'output_servicestate_abbr',
490           'show_blacklist'  => 'output_blacklist',
491           'htmlinfo'        => 'output_html',
492           'okinfo'          => 'output_ok_verbosity',
493           'protocol'        => 'snmp_version',
494           'community'       => 'snmp_community',
495           'port'            => 'snmp_port',
496           'ipv6'            => 'snmp_use_ipv6',
497           'tcp'             => 'snmp_use_tcp',
498           'warning'         => 'temp_threshold_warning',
499           'critical'        => 'temp_threshold_critical',
500           'all'             => 'check_everything',
501           'perfdata'        => 'performance_data',
502           'tempunit'        => 'temperature_unit',
503           'timeout'         => 'timeout',
504           'blacklist'       => 'blacklist',
505           'legacy_perfdata' => 'legacy_performance_data',
506         );
507
508     # Load the perl module
509     if ( eval { require Config::Tiny; 1 } ) {
510         $tiny = Config::Tiny->new();
511     }
512     else {
513         print "ERROR: Required perl module 'Config::Tiny' not found\n";
514         exit $E_UNKNOWN;
515     }
516
517     # Read the config file
518     $tiny = Config::Tiny->read($opt{configfile})
519       or do { report('other', (sprintf q{Couldn't read configuration file: %s}, Config::Tiny->errstr()), $E_UNKNOWN);
520               return; };
521
522     # Syntax check
523     foreach my $section (keys %{ $tiny }) {
524       KEYWORD:
525         foreach my $keyword (keys %{ $tiny->{$section} }) {
526             next KEYWORD if $keyword eq 'check_everything';
527             if ($keyword =~ m{\A check_(.+)}xms) {
528                 my $c = $1;
529                 foreach my $cl (keys %check) {
530                     next KEYWORD if $c eq $cl;
531                 }
532             }
533             else {
534               LEGAL:
535                 foreach my $legal (keys %opt2config) {
536                     next KEYWORD if $keyword eq $opt2config{$legal};
537                 }
538             }
539             if ($section eq '_') {
540                 report('other', qq{CONFIG ERROR: In the global section: Unknown statement "$keyword"}, $E_UNKNOWN);
541             }
542             else {
543                 report('other', qq{CONFIG ERROR: Unknown statement "$keyword" in section "$section"}, $E_UNKNOWN);
544             }
545         }
546     }
547
548     # Adjust checks according to statements in the configuration file
549     sub configfile_adjust_checks {
550         my $keyword = shift;
551       CHECK_CONFIG:
552         foreach my $key (keys %check) {
553             my $copt = join '_', 'check', $key;
554             next CHECK_CONFIG if !defined $tiny->{$keyword}->{$copt} or $tiny->{$keyword}->{$copt} eq q{};
555             if ($tiny->{$keyword}->{$copt} =~ m{$on}ixms) {
556                 $check{$key} = 1;
557             }
558             elsif ($tiny->{$keyword}->{$copt} =~ m{$off}ixms) {
559                 $check{$key} = 0;
560             }
561             else {
562                 report('other', "CONFIG ERROR: Rvalue for '$copt' must be boolean (True/False)", $E_UNKNOWN);
563             }
564         }
565         return;
566     }
567
568     # Set blacklist according to statements in the configuration file
569     sub configfile_set_blacklist {
570         my $keyword = shift;
571         if (defined $tiny->{$keyword}->{blacklist} and $tiny->{$keyword}->{blacklist} ne q{}) {
572             # set_blacklist() takes an array ref
573             set_blacklist([$tiny->{$keyword}->{blacklist}]);
574         }
575         return;
576     }
577
578     # Set timeout according to statements in the configuration file
579     sub configfile_set_timeout {
580         my $keyword = shift;
581         if (defined $tiny->{$keyword}->{timeout} and $tiny->{$keyword}->{timeout} ne q{}) {
582             if ($tiny->{$keyword}->{timeout} =~ m{\A \d+ \z}xms) { # integer
583                 $opt{timeout} = $tiny->{$keyword}->{timeout};
584             }
585             else {
586                 report('other', "CONFIG ERROR: Rvalue for 'timeout' must be a positive integer", $E_UNKNOWN);
587             }
588         }
589         return;
590     }
591
592     # Set a boolean option
593     sub configfile_set_boolean {
594         my ($keyword, $bool) = @_;
595         my $cbool = $opt2config{$bool};
596         if (defined $tiny->{$keyword}->{$cbool} and $tiny->{$keyword}->{$cbool} ne q{}) {
597             if ($tiny->{$keyword}->{$cbool} =~ m{$on}ixms) {
598                 $opt{$bool} = 1;
599             }
600             elsif ($tiny->{$keyword}->{$cbool} =~ m{$off}ixms) {
601                 $opt{$bool} = 0;
602             }
603             else {
604                 report('other', "CONFIG ERROR: Rvalue for '$cbool' must be boolean (True/False)", $E_UNKNOWN);
605             }
606         }
607         return;
608     }
609
610     # Set htmlinfo option from config file
611     sub configfile_set_htmlinfo {
612         my $keyword = shift;
613         my $conf = $opt2config{htmlinfo};
614         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
615             if ($tiny->{$keyword}->{$conf} =~ m{$on}ixms) {
616                 $opt{htmlinfo} = 1;
617             }
618             elsif ($tiny->{$keyword}->{$conf} =~ m{$off}ixms) {
619                 $opt{htmlinfo} = undef;
620             }
621             else {
622                 $opt{htmlinfo} = $tiny->{$keyword}->{$conf};
623             }
624         }
625         return;
626     }
627
628     # Set OK output verbosity
629     sub configfile_set_ok_verbosity {
630         my $keyword = shift;
631         my $conf = $opt2config{okinfo};
632         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
633             if ($tiny->{$keyword}->{$conf} =~ m{\A \d+ \z}xms) {
634                 $opt{okinfo} = $tiny->{$keyword}->{$conf};
635             }
636             else {
637                 report('other', "CONFIG ERROR: Rvalue for '$conf' must be a positive integer", $E_UNKNOWN);
638             }
639         }
640         return;
641     }
642
643     # Set SNMP protocol version from config file
644     sub configfile_set_snmp_version {
645         my $keyword = shift;
646         my $conf = $opt2config{protocol};
647         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
648             if ($tiny->{$keyword}->{$conf} =~ m{\A 1|2|3 \z}xms) {
649                 $opt{protocol} = $tiny->{$keyword}->{$conf};
650             }
651             else {
652                 report('other', "CONFIG ERROR: Rvalue for '$conf' must be '1', '2' or '3'", $E_UNKNOWN);
653             }
654         }
655         return;
656     }
657
658     # Set SNMP community name from config file
659     sub configfile_set_snmp_community {
660         my $keyword = shift;
661         my $conf = $opt2config{community};
662         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
663             $opt{community} = $tiny->{$keyword}->{$conf};
664         }
665         return;
666     }
667
668     # Set SNMP port number from config file
669     sub configfile_set_snmp_port {
670         my $keyword = shift;
671         my $conf = $opt2config{port};
672         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
673             if ($tiny->{$keyword}->{$conf} =~ m{\A \d+ \z}xms) { # integer
674                 $opt{port} = $tiny->{$keyword}->{$conf};
675             }
676             else {
677                 report('other', "CONFIG ERROR: Rvalue for '$conf' must be a positive integer", $E_UNKNOWN);
678             }
679         }
680         return;
681     }
682
683     # Set temperature threshold from config file
684     sub configfile_set_temp_threshold {
685         my $keyword = shift;
686         my $level = shift;
687         my $conf = $opt2config{$level};
688         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
689             $opt{$level} = [$tiny->{$keyword}->{$conf}]; # array ref
690         }
691         return;
692     }
693
694     # Set perfdata from config file
695     sub configfile_set_perfdata {
696         my $keyword = shift;
697         my $conf = $opt2config{perfdata};
698         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
699             if ($tiny->{$keyword}->{$conf} =~ m{$on}ixms) {
700                 $opt{perfdata} = 1;
701             }
702             elsif ($tiny->{$keyword}->{$conf} =~ m{$off}ixms) {
703                 $opt{perfdata} = undef;
704             }
705             elsif ($tiny->{$keyword}->{$conf} =~ m{\A minimal|multiline \z}xms) {
706                 $opt{perfdata} = $tiny->{$keyword}->{$conf};
707             }
708             else {
709                 report('other', "CONFIG ERROR: Rvalue for '$conf' must be either boolean, 'minimal' or 'multiline'", $E_UNKNOWN);
710             }
711         }
712         return;
713     }
714
715     # Set temp unit from config file
716     sub configfile_set_tempunit {
717         my $keyword = shift;
718         my $conf = $opt2config{tempunit};
719         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
720             if ($tiny->{$keyword}->{$conf} =~ m{\A C|F|K|R \z}ixms) {
721                 $opt{tempunit} = $tiny->{$keyword}->{$conf};
722             }
723             else {
724                 report('other', "CONFIG ERROR: Rvalue for '$conf' must one of C/F/K/R", $E_UNKNOWN);
725             }
726         }
727         return;
728     }
729
730     # Set postmsg string from config file
731     sub configfile_set_postmsg {
732         my $keyword = shift;
733         my $conf = $opt2config{postmsg};
734         if (defined $tiny->{$keyword}->{$conf} and $tiny->{$keyword}->{$conf} ne q{}) {
735             $opt{postmsg} = $tiny->{$keyword}->{$conf}; # array ref
736         }
737         return;
738     }
739
740     # Sections in the config file to check for statements
741     my @sections = ();
742
743     # First: Populate the sections array with the global section
744     @sections = ('_');
745
746     # Second: Populate the sections array with host glob pattern (but
747     # not exact match)
748   PATTERN:
749     foreach my $glob (sort keys %{ $tiny }) {
750         next PATTERN if $glob eq '_';            # global section
751         next PATTERN if $glob eq $opt{hostname}; # exact match
752         my $regex = glob2regex($glob);           # make regexp
753         if ($opt{hostname} =~ m{$regex}) {
754             push @sections, $glob;
755         }
756     }
757
758     # Third: Populate the sections array with exact hostname
759     if (defined $tiny->{$opt{hostname}}) {
760         push @sections, $opt{hostname};
761     }
762
763     # Loop through the sections array and get options
764     foreach my $sect (@sections) {
765         configfile_adjust_checks($sect);
766         configfile_set_blacklist($sect);
767         configfile_set_timeout($sect);
768         configfile_set_htmlinfo($sect);
769         configfile_set_ok_verbosity($sect);
770         configfile_set_boolean($sect, 'all');
771         configfile_set_boolean($sect, 'info');
772         configfile_set_boolean($sect, 'extinfo');
773         configfile_set_boolean($sect, 'state');
774         configfile_set_boolean($sect, 'shortstate');
775         configfile_set_boolean($sect, 'show_blacklist');
776         configfile_set_boolean($sect, 'ipv6');
777         configfile_set_boolean($sect, 'tcp');
778         configfile_set_boolean($sect, 'legacy_perfdata');
779         configfile_set_snmp_version($sect);
780         configfile_set_snmp_community($sect);
781         configfile_set_snmp_port($sect);
782         configfile_set_temp_threshold($sect, 'warning');
783         configfile_set_temp_threshold($sect, 'critical');
784         configfile_set_perfdata($sect);
785         configfile_set_tempunit($sect);
786         configfile_set_postmsg($sect);
787     }
788
789     return;
790 }
791
792 #
793 # Store a message in one of the message arrays
794 #
795 sub report {
796     my ($type, $msg, $exval, $id) = @_;
797     defined $id or $id = q{};
798
799     my %type2array
800       = (
801          'storage' => \@report_storage,
802          'chassis' => \@report_chassis,
803          'other'   => \@report_other,
804         );
805
806     return push @{ $type2array{$type} }, [ $msg, $exval, $id ];
807 }
808
809 #
810 # Run command, put resulting output lines in an array and return a
811 # pointer to that array
812 #
813 sub run_command {
814     my $command = shift;
815
816     open my $CMD, '-|', $command
817       or do { report('other', "Couldn't run command '$command': $!", $E_UNKNOWN)
818                 and return [] };
819     my @lines = <$CMD>;
820     close $CMD
821       or do { report('other', "Couldn't close filehandle for command '$command': $!", $E_UNKNOWN)
822                 and return \@lines };
823     return \@lines;
824 }
825
826 #
827 # Run command, put resulting output in a string variable and return it
828 #
829 sub slurp_command {
830     my $command = shift;
831
832     open my $CMD, '-|', $command
833       or do { report('other', "Couldn't run command '$command': $!", $E_UNKNOWN) and return };
834     my $rawtext = do { local $/ = undef; <$CMD> }; # slurping
835     close $CMD;
836
837     # NOTE: We don't check the return value of close() since omreport
838     # does something weird sometimes.
839
840     return $rawtext;
841 }
842
843 #
844 # Initialize SNMP
845 #
846 sub snmp_initialize {
847     # Legal SNMP v3 protocols
848     my $snmp_v3_privprotocol = qr{\A des|aes|aes128|3des|3desde \z}xms;
849     my $snmp_v3_authprotocol = qr{\A md5|sha \z}xms;
850
851     # Parameters to Net::SNMP->session()
852     my %param
853       = (
854          '-port'     => $opt{port},
855          '-hostname' => $opt{hostname},
856          '-version'  => $opt{protocol},
857         );
858
859     # Setting the domain (IP version and transport protocol)
860     my $transport = $opt{tcp} ? 'tcp' : 'udp';
861     my $ipversion = $opt{ipv6} ? 'ipv6' : 'ipv4';
862     $param{'-domain'} = "$transport/$ipversion";
863
864     # Parameters for SNMP v3
865     if ($opt{protocol} == 3) {
866
867         # Username is mandatory
868         if (defined $opt{username}) {
869             $param{'-username'} = $opt{username};
870         }
871         else {
872             print "SNMP ERROR: With SNMPv3 the username must be specified\n";
873             exit $E_UNKNOWN;
874         }
875
876         # Authpassword is optional
877         if (defined $opt{authpassword}) {
878             $param{'-authpassword'} = $opt{authpassword};
879         }
880
881         # Authkey is optional
882         if (defined $opt{authkey}) {
883             $param{'-authkey'} = $opt{authkey};
884         }
885
886         # Privpassword is optional
887         if (defined $opt{privpassword}) {
888             $param{'-privpassword'} = $opt{privpassword};
889         }
890
891         # Privkey is optional
892         if (defined $opt{privkey}) {
893             $param{'-privkey'} = $opt{privkey};
894         }
895
896         # Privprotocol is optional
897         if (defined $opt{privprotocol}) {
898             if ($opt{privprotocol} =~ m/$snmp_v3_privprotocol/xms) {
899                 $param{'-privprotocol'} = $opt{privprotocol};
900             }
901             else {
902                 print "SNMP ERROR: Unknown privprotocol '$opt{privprotocol}', "
903                   . "must be one of [des|aes|aes128|3des|3desde]\n";
904                 exit $E_UNKNOWN;
905             }
906         }
907
908         # Authprotocol is optional
909         if (defined $opt{authprotocol}) {
910             if ($opt{authprotocol} =~ m/$snmp_v3_authprotocol/xms) {
911                 $param{'-authprotocol'} = $opt{authprotocol};
912             }
913             else {
914                 print "SNMP ERROR: Unknown authprotocol '$opt{authprotocol}', "
915                   . "must be one of [md5|sha]\n";
916                 exit $E_UNKNOWN;
917             }
918         }
919     }
920     # Parameters for SNMP v2c or v1
921     elsif ($opt{protocol} == 2 or $opt{protocol} == 1) {
922         $param{'-community'} = $opt{community};
923     }
924     else {
925         print "SNMP ERROR: Unknown SNMP version '$opt{protocol}'\n";
926         exit $E_UNKNOWN;
927     }
928
929     # Try to initialize the SNMP session
930     if ( eval { require Net::SNMP; 1 } ) {
931         ($snmp_session, $snmp_error) = Net::SNMP->session( %param );
932         if (!defined $snmp_session) {
933             printf "SNMP: %s\n", $snmp_error;
934             exit $E_UNKNOWN;
935         }
936     }
937     else {
938         print "ERROR: You need perl module Net::SNMP to run $NAME in SNMP mode\n";
939         exit $E_UNKNOWN;
940     }
941     return;
942 }
943
944 #
945 # Checking if SNMP works by probing for "chassisModelName", which all
946 # servers should have
947 #
948 sub snmp_check {
949     my $chassisModelName = '1.3.6.1.4.1.674.10892.1.300.10.1.9.1';
950     my $result = $snmp_session->get_request(-varbindlist => [$chassisModelName]);
951
952     # Typically if remote host isn't responding
953     if (!defined $result) {
954         printf "SNMP CRITICAL: %s\n", $snmp_session->error;
955         exit $E_CRITICAL;
956     }
957
958     # If OpenManage isn't installed or is not working
959     if ($result->{$chassisModelName} =~ m{\A noSuch (Instance|Object) \z}xms) {
960         print "ERROR: (SNMP) OpenManage is not installed or is not working correctly\n";
961         exit $E_UNKNOWN;
962     }
963     return;
964 }
965
966 #
967 # Detecting blade via SNMP
968 #
969 sub snmp_detect_blade {
970     # In some setups, the IDs for the blade and interconnect
971     # board are mixed up, so we need to check both.
972     my $DellBaseBoardType1 = '1.3.6.1.4.1.674.10892.1.300.80.1.7.1.1';
973     my $DellBaseBoardType2 = '1.3.6.1.4.1.674.10892.1.300.80.1.7.1.2';
974     my $result1 = $snmp_session->get_request(-varbindlist => [$DellBaseBoardType1]);
975     my $result2 = $snmp_session->get_request(-varbindlist => [$DellBaseBoardType2]);
976
977     # Identify blade. Older models (4th and 5th gen models) and/or old
978     # OMSA (4.x) don't have this OID. If we get "noSuchInstance" or
979     # similar, we assume that this isn't a blade
980     if (exists $result1->{$DellBaseBoardType1} && $result1->{$DellBaseBoardType1} eq '3') {
981         $blade = 1;
982         return;
983     }
984     if (exists $result2->{$DellBaseBoardType2} && $result2->{$DellBaseBoardType2} eq '3') {
985         $blade = 1;
986         return;
987     }
988     return;
989 }
990
991 #
992 # Locate the omreport binary
993 #
994 sub find_omreport {
995     # If user has specified path to omreport
996     if (defined $opt{omreport} and -x $opt{omreport}) {
997         $omreport = qq{"$opt{omreport}"};
998         return;
999     }
1000
1001     # Possible full paths for omreport
1002     my @omreport_paths
1003       = (
1004          '/opt/dell/srvadmin/bin/omreport',              # default on Linux with OMSA >= 6.2.0
1005          '/usr/bin/omreport',                            # default on Linux with OMSA < 6.2.0
1006          '/opt/dell/srvadmin/oma/bin/omreport.sh',       # alternate on Linux
1007          '/opt/dell/srvadmin/oma/bin/omreport',          # alternate on Linux
1008          'C:\Program Files (x86)\Dell\SysMgt\oma\bin\omreport.exe', # default on Windows x64
1009          'C:\Program Files\Dell\SysMgt\oma\bin\omreport.exe',       # default on Windows x32
1010          'c:\progra~1\dell\sysmgt\oma\bin\omreport.exe', # 8bit legacy default on Windows x32
1011          'c:\progra~2\dell\sysmgt\oma\bin\omreport.exe', # 8bit legacy default on Windows x64
1012         );
1013
1014     # Find the one to use
1015   OMREPORT_PATH:
1016     foreach my $bin (@omreport_paths) {
1017         if (-x $bin) {
1018             $omreport = qq{"$bin"};
1019             last OMREPORT_PATH;
1020         }
1021     }
1022
1023     # Exit with status=UNKNOWN if OM is not installed, or we don't
1024     # have permission to execute the binary
1025     if (!defined $omreport) {
1026         print "ERROR: Dell OpenManage Server Administrator (OMSA) is not installed\n";
1027         exit $E_UNKNOWN;
1028     }
1029     return;
1030 }
1031
1032 #
1033 # Checks output from 'omreport -?' and searches for arguments to
1034 # omreport, to accommodate deprecated options "chassis" and "system"
1035 # (on newer hardware), as well as blade servers.
1036 #
1037 sub check_omreport_options {
1038     foreach (@{ run_command("$omreport -? 2>&1") }) {
1039        if (m/\A servermodule /xms) {
1040            # If "servermodule" argument to omreport exists, use it
1041            # instead of argument "system"
1042            $omopt_system = 'servermodule';
1043        }
1044        elsif (m/\A mainsystem /xms) {
1045            # If "mainsystem" argument to omreport exists, use it
1046            # instead of argument "chassis"
1047            $omopt_chassis = 'mainsystem';
1048        }
1049        elsif (m/\A modularenclosure /xms) {
1050            # If "modularenclusure" argument to omreport exists, assume
1051            # that this is a blade
1052            $blade = 1;
1053        }
1054     }
1055     return;
1056 }
1057
1058 #
1059 # Read the blacklist option and return a hash containing the
1060 # blacklisted components
1061 #
1062 sub set_blacklist {
1063     my $foo = shift;
1064     my @bl = ();
1065
1066     if (scalar @{ $foo } >= 0) {
1067         foreach my $black (@{ $foo }) {
1068             my $tmp = q{};
1069             if (-f $black) {
1070                 open my $BL, '<', $black
1071                   or do { report('other', "Couldn't open blacklist file $black: $!", $E_UNKNOWN)
1072                             and return {} };
1073                 chomp($tmp = <$BL>);
1074                 close $BL;
1075             }
1076             else {
1077                 $tmp = $black;
1078             }
1079             push @bl, $tmp;
1080         }
1081     }
1082
1083     return {} if $#bl < 0;
1084
1085     # Parse blacklist string, put in hash
1086     foreach my $black (@bl) {
1087         my @comps = split m{/}xms, $black;
1088         foreach my $c (@comps) {
1089             next if $c !~ m/=/xms;
1090             my ($key, $val) = split /=/xms, $c;
1091             my @vals = split /,/xms, $val;
1092             push @{ $blacklist{$key} }, @vals;
1093         }
1094     }
1095
1096     return;
1097 }
1098
1099 #
1100 # Read the check option and adjust the hash %check, which is a rough
1101 # list of components to be checked
1102 #
1103 sub adjust_checks {
1104     my @cl = ();
1105
1106     # First, take the '--no-storage' option
1107     if ($opt{no_storage}) {
1108         $check{storage} = 0;
1109     }
1110
1111     # Adjust checking based on the '--all' option
1112     if ($opt{all}) {
1113         # Check option usage
1114         if (defined $opt{only} and $opt{only} !~ m{\A critical|warning \z}xms) {
1115             print qq{ERROR: Wrong simultaneous usage of the "--all" and "--only" options\n};
1116             exit $E_UNKNOWN;
1117         }
1118         if (scalar @{ $opt{check} } > 0) {
1119             print qq{ERROR: Wrong simultaneous usage of the "--all" and "--check" options\n};
1120             exit $E_UNKNOWN;
1121         }
1122
1123         # set the check hash to check everything
1124         map { $_ = 1 } values %check;
1125
1126         return;
1127     }
1128
1129     # Adjust checking based on the '--only' option
1130     if (defined $opt{only} and $opt{only} !~ m{\A critical|warning \z}xms) {
1131         # Check option usage
1132         if (scalar @{ $opt{check} } > 0) {
1133             print qq{ERROR: Wrong simultaneous usage of the "--only" and "--check" options\n};
1134             exit $E_UNKNOWN;
1135         }
1136         if (! exists $check{$opt{only}} && $opt{only} ne 'chassis') {
1137             print qq{ERROR: "$opt{only}" is not a known keyword for the "--only" option\n};
1138             exit $E_UNKNOWN;
1139         }
1140
1141         # reset the check hash
1142         map { $_ = 0 } values %check;
1143
1144         # adjust the check hash
1145         if ($opt{only} eq 'chassis') {
1146             map { $check{$_} = 1 } qw(memory fans power temp cpu voltage sdcard
1147                                       batteries amperage intrusion esmhealth);
1148         }
1149         else {
1150             $check{$opt{only}} = 1;
1151         }
1152
1153         return;
1154     }
1155
1156     # Adjust checking based on the '--check' option
1157     if (scalar @{ $opt{check} } >= 0) {
1158         foreach my $check (@{ $opt{check} }) {
1159             my $tmp = q{};
1160             if (-f $check) {
1161                 open my $CL, '<', $check
1162                   or do { report('other', "Couldn't open check file $check: $!", $E_UNKNOWN) and return };
1163                 chomp($tmp = <$CL>);
1164                 close $CL;
1165             }
1166             else {
1167                 $tmp = $check;
1168             }
1169             push @cl, $tmp;
1170         }
1171     }
1172
1173     return if $#cl < 0;
1174
1175     # Parse checklist string, put in hash
1176     foreach my $check (@cl) {
1177         my @checks = split /,/xms, $check;
1178         foreach my $c (@checks) {
1179             next if $c !~ m/=/xms;
1180             my ($key, $val) = split /=/xms, $c;
1181             $check{$key} = $val;
1182         }
1183     }
1184
1185     # Check if we should check global health status
1186   CHECK_KEY:
1187     foreach (keys %check) {
1188         next CHECK_KEY if $_ eq 'esmlog';   # not part of global status
1189         next CHECK_KEY if $_ eq 'alertlog'; # not part of global status
1190
1191         if ($check{$_} == 0) { # found something with checking turned off
1192             $global = 0;
1193             last CHECK_KEY;
1194         }
1195     }
1196
1197     return;
1198 }
1199
1200 #
1201 # Runs omreport and returns an array of anonymous hashes containing
1202 # the output.
1203 # Takes one argument: string containing parameters to omreport
1204 #
1205 sub run_omreport {
1206     my $command = shift;
1207     my @output  = ();
1208     my @keys    = ();
1209
1210     # Errors that are OK. Some low-end poweredge (and blades) models
1211     # don't have RAID controllers, intrusion detection sensor, or
1212     # redundant/instrumented power supplies etc.
1213     my $ok_errors
1214       = qr{
1215             Intrusion\sinformation\sis\snot\sfound\sfor\sthis\ssystem  # No intrusion probe
1216           | No\sinstrumented\spower\ssupplies\sfound\son\sthis\ssystem # No instrumented PS (blades/low-end)
1217           | No\sbattery\sprobes\sfound\son\sthis\ssystem               # No battery probes
1218           | Invalid\scommand:\spwrmonitoring                           # Old hardware
1219           | Hardware\sor\sfeature\snot\spresent\.                      # SD cards
1220           | Invalid\scommand:\sremovableflashmedia                     # SD cards with old OMSA
1221           | Error\sCorrection;                                         # Memory stuff. Not really an error (new in OMSA 6.4)
1222 #          | Current\sprobes\snot\sfound                                # OMSA + RHEL5.4 bug
1223 #          | No\scontrollers\sfound                                     # No RAID controller
1224         }xms;
1225
1226     # Errors that are OK on blade servers
1227     my $ok_blade_errors
1228       = qr{
1229               No\sfan\sprobes\sfound\son\sthis\ssystem   # No fan probes
1230       }xms;
1231
1232     # Run omreport and fetch output
1233     my $rawtext = slurp_command("$omreport $command -fmt ssv 2>&1");
1234     return [] if !defined $rawtext;
1235
1236     # Workaround for Openmanage BUG introduced in OMSA 5.5.0
1237     $rawtext =~ s{\n;}{;}gxms if $command eq 'storage controller';
1238
1239     # Workaround for logical connectors where there are extra
1240     # information that isn't possible to parse consistently. Remove
1241     # everything after and including "Path Health"
1242     if ($command =~ m{\A storage\sconnector}xms) {
1243         $rawtext =~ s{Path\sHealth.*}{}xms;
1244     }
1245
1246     # Report if no controllers found
1247     if ($command eq 'storage controller' and $rawtext =~ m{No\scontrollers\sfound}xms) {
1248         report('storage', 'Storage Error! No controllers found', $E_UNKNOWN);
1249     }
1250
1251     # Openmanage sometimes puts a linebreak between "Error" and the
1252     # actual error text
1253     $rawtext =~ s{^Error\s*\n}{Error: }xms;
1254
1255     # Parse output, store in array
1256     for ((split m{\n}xms, $rawtext)) {
1257         if (m{\AError}xms) {
1258             next if m{$ok_errors}xms;
1259             next if ($blade and m{$ok_blade_errors}xms);
1260             report('other', "Problem running 'omreport $command': $_", $E_UNKNOWN);
1261         }
1262
1263         next if !m/(.*?;){2}/xms;  # ignore lines with less than 3 fields
1264         my @vals = split /;/xms;
1265         if ($vals[0] =~ m/\A (Index|ID|Severity|Processor|Current\sSpeed|Connector\sName) \z/xms) {
1266             @keys = @vals;
1267         }
1268         else {
1269             my $i = 0;
1270             push @output, { map { $_ => $vals[$i++] } @keys };
1271         }
1272
1273     }
1274
1275     # Finally, return the collected information
1276     return \@output;
1277 }
1278
1279 #
1280 # Checks if a component is blacklisted. Returns 1 if the component is
1281 # blacklisted, 0 otherwise. Takes two arguments:
1282 #   arg1: component name
1283 #   arg2: component id or index
1284 #
1285 sub blacklisted {
1286     my $name = shift;  # component name
1287     my $id   = shift;  # component id
1288     my $ret  = 0;      # return value
1289
1290     if (defined $blacklist{$name}) {
1291         foreach my $comp (@{ $blacklist{$name} }) {
1292             if (defined $id and ($comp eq $id or uc($comp) eq 'ALL')) {
1293                 $ret = 1;
1294             }
1295         }
1296     }
1297
1298     return $ret;
1299 }
1300
1301 # Converts the NexusID from SNMP to our version
1302 sub convert_nexus {
1303     my $nexus = shift;
1304     $nexus =~ s{\A \\}{}xms;
1305     $nexus =~ s{\\}{:}gxms;
1306     return $nexus;
1307 }
1308
1309 # Sets custom temperature thresholds based on user supplied options
1310 sub custom_temperature_thresholds {
1311     my $type   = shift; # type of threshold, either w (warning) or c (critical)
1312     my %thres  = ();    # will contain the thresholds
1313     my @limits = ();    # holds the input
1314
1315     my @opt =  $type eq 'w' ? @{ $opt{warning} } : @{ $opt{critical} };
1316
1317     if (scalar @opt >= 0) {
1318         foreach my $t (@opt) {
1319             my $tmp = q{};
1320             if (-f $t) {
1321                 open my $F, '<', $t
1322                   or do { report('other', "Couldn't open temperature threshold file $t: $!",
1323                                  $E_UNKNOWN) and return {} };
1324                 $tmp = <$F>;
1325                 close $F;
1326             }
1327             else {
1328                 $tmp = $t;
1329             }
1330             push @limits, $tmp;
1331         }
1332     }
1333
1334     # Parse checklist string, put in hash
1335     foreach my $th (@limits) {
1336         my @tmp = split m{,}xms, $th;
1337         foreach my $t (@tmp) {
1338             next if $t !~ m{=}xms;
1339             my ($key, $val) = split m{=}xms, $t;
1340             if ($val =~ m{/}xms) {
1341                 my ($max, $min) = split m{/}xms, $val;
1342                 $thres{$key}{max} = $max;
1343                 $thres{$key}{min} = $min;
1344             }
1345             else {
1346                 $thres{$key}{max} = $val;
1347             }
1348         }
1349     }
1350
1351     return \%thres;
1352 }
1353
1354
1355 # Gets the output from SNMP result according to the OIDs checked
1356 sub get_snmp_output {
1357     my ($result,$oidref) = @_;
1358     my @temp   = ();
1359     my @output = ();
1360
1361     foreach my $oid (keys %{ $result }) {
1362         my $short = $oid;
1363         $short =~ s{\s}{}gxms;                   # remove whitespace
1364         $short =~ s{\A (.+) \. (\d+) \z}{$1}xms; # remove last number
1365         my $id = $2;
1366         if (exists $oidref->{$short}) {
1367             $temp[$id]{$oidref->{$short}} = $result->{$oid};
1368         }
1369     }
1370
1371     # Remove any empty indexes
1372     foreach my $out (@temp) {
1373         if (defined $out) {
1374             push @output, $out;
1375         }
1376     }
1377
1378     return \@output;
1379 }
1380
1381
1382 # Map the controller or other item in-place
1383 sub map_item {
1384     my ($key, $val, $list)  = @_;
1385
1386     foreach my $lst (@{ $list }) {
1387         if (!exists $lst->{$key}) {
1388             $lst->{$key} = $val;
1389         }
1390     }
1391     return;
1392 }
1393
1394 # Return the URL for official Dell documentation for a specific
1395 # PowerEdge server
1396 sub documentation_url {
1397     my $model = shift;
1398
1399     # create model short form, e.g. "r710"
1400     $model =~ s{\A PowerEdge \s (.+?) \z}{lc($1)}exms;
1401
1402     # special case for blades (e.g. M600, M710), they have common
1403     # documentation
1404     $model =~ s{\A m\d+ \z}{m}xms;
1405
1406     return 'http://support.dell.com/support/edocs/systems/pe' . $model . '/';
1407 }
1408
1409 # Return the URL for warranty information for a server with a given
1410 # serial number (servicetag)
1411 sub warranty_url {
1412     my $tag = shift;
1413
1414     # Dell support sites for different parts of the world
1415     my %supportsite
1416       = (
1417          'emea' => 'http://support.euro.dell.com/support/topics/topic.aspx/emea/shared/support/my_systems_info/',
1418          'ap'   => 'http://supportapj.dell.com/support/topics/topic.aspx/ap/shared/support/my_systems_info/en/details?',
1419          'glob' => 'http://support.dell.com/support/topics/global.aspx/support/my_systems_info/details?',
1420         );
1421
1422     # warranty URLs for different country codes
1423     my %url
1424       = (
1425          # EMEA
1426          'at' => $supportsite{emea} . 'de/details?c=at&l=de&ServiceTag=',  # Austria
1427          'be' => $supportsite{emea} . 'nl/details?c=be&l=nl&ServiceTag=',  # Belgium
1428          'cz' => $supportsite{emea} . 'cs/details?c=cz&l=cs&ServiceTag=',  # Czech Republic
1429          'de' => $supportsite{emea} . 'de/details?c=de&l=de&ServiceTag=',  # Germany
1430          'dk' => $supportsite{emea} . 'da/details?c=dk&l=da&ServiceTag=',  # Denmark
1431          'es' => $supportsite{emea} . 'es/details?c=es&l=es&ServiceTag=',  # Spain
1432          'fi' => $supportsite{emea} . 'fi/details?c=fi&l=fi&ServiceTag=',  # Finland
1433          'fr' => $supportsite{emea} . 'fr/details?c=fr&l=fr&ServiceTag=',  # France
1434          'gr' => $supportsite{emea} . 'en/details?c=gr&l=el&ServiceTag=',  # Greece
1435          'it' => $supportsite{emea} . 'it/details?c=it&l=it&ServiceTag=',  # Italy
1436          'il' => $supportsite{emea} . 'en/details?c=il&l=en&ServiceTag=',  # Israel
1437          'me' => $supportsite{emea} . 'en/details?c=me&l=en&ServiceTag=',  # Middle East
1438          'no' => $supportsite{emea} . 'no/details?c=no&l=no&ServiceTag=',  # Norway
1439          'nl' => $supportsite{emea} . 'nl/details?c=nl&l=nl&ServiceTag=',  # The Netherlands
1440          'pl' => $supportsite{emea} . 'pl/details?c=pl&l=pl&ServiceTag=',  # Poland
1441          'pt' => $supportsite{emea} . 'en/details?c=pt&l=pt&ServiceTag=',  # Portugal
1442          'ru' => $supportsite{emea} . 'ru/details?c=ru&l=ru&ServiceTag=',  # Russia
1443          'se' => $supportsite{emea} . 'sv/details?c=se&l=sv&ServiceTag=',  # Sweden
1444          'uk' => $supportsite{emea} . 'en/details?c=uk&l=en&ServiceTag=',  # United Kingdom
1445          'za' => $supportsite{emea} . 'en/details?c=za&l=en&ServiceTag=',  # South Africa
1446          # America
1447          'br' => $supportsite{glob} . 'c=br&l=pt&ServiceTag=',  # Brazil
1448          'ca' => $supportsite{glob} . 'c=ca&l=en&ServiceTag=',  # Canada
1449          'mx' => $supportsite{glob} . 'c=mx&l=es&ServiceTag=',  # Mexico
1450          'us' => $supportsite{glob} . 'c=us&l=en&ServiceTag=',  # USA
1451          # Asia/Pacific
1452          'au' => $supportsite{ap} . 'c=au&l=en&ServiceTag=',  # Australia
1453          'cn' => $supportsite{ap} . 'c=cn&l=zh&ServiceTag=',  # China
1454          'in' => $supportsite{ap} . 'c=in&l=en&ServiceTag=',  # India
1455          # default fallback
1456          'XX' => $supportsite{glob} . 'ServiceTag=',  # default
1457         );
1458
1459     if (exists $url{$opt{htmlinfo}}) {
1460         return $url{$opt{htmlinfo}} . $tag;
1461     }
1462     else {
1463         return $url{XX} . $tag;
1464     }
1465 }
1466
1467
1468 # This helper function returns the corresponding value of a hash key,
1469 # but takes into account that the key may not exist
1470 sub get_hashval {
1471     my $key  = shift || return;
1472     my $hash = shift;
1473     return defined $hash->{$key} ? $hash->{$key} : "Undefined value $key";
1474 }
1475
1476 # Find component status from hash
1477 sub get_snmp_status {
1478     my $key  = shift || return 'Unknown';
1479     return exists $snmp_status{$key} ? $snmp_status{$key} : 'Unknown';
1480 }
1481
1482 # Find component status from hash
1483 sub get_snmp_probestatus {
1484     my $key  = shift || return 'Unknown';
1485     return exists $snmp_probestatus{$key} ? $snmp_probestatus{$key} : 'Unknown';
1486 }
1487
1488 # Check that a hash entry is defined and not an empty string. Return a
1489 # chosen string (parameter) if these conditions are not met
1490 sub get_nonempty_string {
1491     my $key  = shift;  # key to check
1492     my $hash = shift;  # hash where the key belongs
1493     my $alt  = shift;  # alternate return value
1494     if (defined $hash->{$key} and $hash->{$key} ne q{}) {
1495         return $hash->{$key};
1496     }
1497     return $alt;
1498 }
1499
1500 # Converts from Celsius to something else
1501 sub temp_from_celsius {
1502     my $x  = shift;
1503     my $to = shift;
1504
1505     if ($to eq 'F') {
1506         return sprintf '%.1f', ($x * 9/5 + 32);
1507     }
1508     elsif ($to eq 'K') {
1509         return sprintf '%.1f', ($x + 273.15);
1510     }
1511     elsif ($to eq 'R') {
1512         return sprintf '%.1f', ($x * 9/5 + 32 + 459.67);
1513     }
1514     return $x;
1515 }
1516
1517
1518 #---------------------------------------------------------------------
1519 # Check functions
1520 #---------------------------------------------------------------------
1521
1522 #-----------------------------------------
1523 # Check global health status
1524 #-----------------------------------------
1525 sub check_global {
1526     my $health = $E_OK;
1527
1528     if ($snmp) {
1529         #
1530         # Checks global status, i.e. both storage and chassis
1531         #
1532         my $systemStateGlobalSystemStatus = '1.3.6.1.4.1.674.10892.1.200.10.1.2.1';
1533         my $result = $snmp_session->get_request(-varbindlist => [$systemStateGlobalSystemStatus]);
1534         if (!defined $result) {
1535             printf "SNMP ERROR [global]: %s\n", $snmp_error;
1536             exit $E_UNKNOWN;
1537         }
1538         $health = $status2nagios{get_snmp_status($result->{$systemStateGlobalSystemStatus})};
1539     }
1540     else {
1541         #
1542         # NB! This does not check storage, only chassis...
1543         #
1544         foreach (@{ run_command("$omreport $omopt_system -fmt ssv") }) {
1545             next if !m/;/xms;
1546             next if m/\A SEVERITY;COMPONENT/xms;
1547             if (m/\A (.+?);Main\sSystem(\sChassis)? /xms) {
1548                 $health = $status2nagios{$1};
1549                 last;
1550             }
1551         }
1552     }
1553
1554     # Return the status
1555     return $health;
1556 }
1557
1558
1559 #-----------------------------------------
1560 # STORAGE: Check controllers
1561 #-----------------------------------------
1562 sub check_controllers {
1563     my $nexus    = undef;
1564     my $name     = undef;
1565     my $state    = undef;
1566     my $status   = undef;
1567     my $minfw    = undef;
1568     my $mindr    = undef;
1569     my $firmware = undef;
1570     my $driver   = undef;
1571     my $minstdr  = undef;  # Minimum required Storport driver version
1572     my $stdr     = undef;  # Storport driver version
1573     my @output   = ();
1574
1575     if ($snmp) {
1576         my %ctrl_oid
1577           = (
1578              '1.3.6.1.4.1.674.10893.1.20.130.1.1.1'  => 'controllerNumber',
1579              '1.3.6.1.4.1.674.10893.1.20.130.1.1.2'  => 'controllerName',
1580              '1.3.6.1.4.1.674.10893.1.20.130.1.1.5'  => 'controllerState',
1581              '1.3.6.1.4.1.674.10893.1.20.130.1.1.8'  => 'controllerFWVersion',
1582              '1.3.6.1.4.1.674.10893.1.20.130.1.1.38' => 'controllerComponentStatus',
1583              '1.3.6.1.4.1.674.10893.1.20.130.1.1.39' => 'controllerNexusID',
1584              '1.3.6.1.4.1.674.10893.1.20.130.1.1.41' => 'controllerDriverVersion',
1585              '1.3.6.1.4.1.674.10893.1.20.130.1.1.44' => 'controllerMinFWVersion',
1586              '1.3.6.1.4.1.674.10893.1.20.130.1.1.45' => 'controllerMinDriverVersion',
1587              '1.3.6.1.4.1.674.10893.1.20.130.1.1.55' => 'controllerStorportDriverVersion',
1588              '1.3.6.1.4.1.674.10893.1.20.130.1.1.56' => 'controllerMinRequiredStorportVer',
1589             );
1590
1591         # We use get_table() here for the odd case where a server has
1592         # two or more controllers, and where some OIDs are missing on
1593         # one of the controllers.
1594         my $controllerTable = '1.3.6.1.4.1.674.10893.1.20.130.1';
1595         my $result = $snmp_session->get_table(-baseoid => $controllerTable);
1596
1597         if (!defined $result) {
1598             report('storage', 'Storage Error! No controllers found', $E_UNKNOWN);
1599             return;
1600         }
1601
1602         @output = @{ get_snmp_output($result, \%ctrl_oid) };
1603     }
1604     else {
1605         @output = @{ run_omreport('storage controller') };
1606     }
1607
1608     my %ctrl_state
1609       = (
1610          0 => 'Unknown',
1611          1 => 'Ready',
1612          2 => 'Failed',
1613          3 => 'Online',
1614          4 => 'Offline',
1615          6 => 'Degraded',
1616         );
1617
1618   CTRL:
1619     foreach my $out (@output) {
1620         if ($snmp) {
1621             $name     = $out->{controllerName} || 'Unknown controller';
1622             $state    = get_hashval($out->{controllerState}, \%ctrl_state) || 'Unknown state';
1623             $status   = get_snmp_status($out->{controllerComponentStatus});
1624             $minfw    = $out->{controllerMinFWVersion} || undef;
1625             $mindr    = $out->{controllerMinDriverVersion} || undef;
1626             $firmware = $out->{controllerFWVersion} || 'N/A';
1627             $driver   = $out->{controllerDriverVersion} || 'N/A';
1628             $minstdr  = $out->{'controllerMinRequiredStorportVer'} || undef;
1629             $stdr     = $out->{controllerStorportDriverVersion} || undef;
1630             $nexus    = convert_nexus(($out->{controllerNexusID} || 9999));
1631         }
1632         else {
1633             $nexus    = get_nonempty_string('ID', $out, '9999');
1634             $name     = get_nonempty_string('Name', $out, 'Unknown controller');
1635             $state    = get_nonempty_string('State', $out, 'Unknown state');
1636             $status   = get_nonempty_string('Status', $out, 'Unknown');
1637             $minfw    = $out->{'Minimum Required Firmware Version'} ne 'Not Applicable'
1638               ? $out->{'Minimum Required Firmware Version'} : undef;
1639             $mindr    = $out->{'Minimum Required Driver Version'} ne 'Not Applicable'
1640               ? $out->{'Minimum Required Driver Version'} : undef;
1641             $firmware = $out->{'Firmware Version'} ne 'Not Applicable'
1642               ? $out->{'Firmware Version'} : 'N/A';
1643             $driver   = $out->{'Driver Version'} ne 'Not Applicable'
1644               ? $out->{'Driver Version'} : 'N/A';
1645             $minstdr  = (exists $out->{'Minimum Required Storport Driver Version'}
1646                          and $out->{'Minimum Required Storport Driver Version'} ne 'Not Applicable')
1647               ? $out->{'Minimum Required Storport Driver Version'} : undef;
1648             $stdr     = (exists $out->{'Storport Driver Version'}
1649                          and $out->{'Storport Driver Version'} ne 'Not Applicable')
1650               ? $out->{'Storport Driver Version'} : undef;
1651         }
1652
1653         $name =~ s{\s+\z}{}xms; # remove trailing whitespace
1654         push @controllers, $nexus;
1655
1656         # Collecting some storage info
1657         $sysinfo{'controller'}{$nexus}{'id'}       = $nexus;
1658         $sysinfo{'controller'}{$nexus}{'name'}     = $name;
1659         $sysinfo{'controller'}{$nexus}{'driver'}   = $driver;
1660         $sysinfo{'controller'}{$nexus}{'firmware'} = $firmware;
1661         $sysinfo{'controller'}{$nexus}{'storport'} = $stdr;
1662
1663         # Store controller info for future use (SNMP)
1664         if ($snmp) {
1665             $snmp_controller{$out->{controllerNumber}} = $nexus;
1666         }
1667
1668         next CTRL if blacklisted('ctrl', $nexus);
1669
1670         # Special case: old firmware
1671         if (!blacklisted('ctrl_fw', $nexus) && defined $minfw) {
1672             chomp $firmware;
1673             my $msg = sprintf q{Controller %d [%s]: Firmware '%s' is out of date},
1674               $nexus, $name, $firmware;
1675             report('storage', $msg, $E_WARNING, $nexus);
1676         }
1677         # Special case: old driver
1678         if (!blacklisted('ctrl_driver', $nexus) && defined $mindr) {
1679             chomp $driver;
1680             my $msg = sprintf q{Controller %d [%s]: Driver '%s' is out of date},
1681               $nexus, $name, $driver;
1682             report('storage', $msg, $E_WARNING, $nexus);
1683         }
1684         # Special case: old storport driver
1685         if (!blacklisted('ctrl_stdr', $nexus) && defined $minstdr) {
1686             chomp $stdr;
1687             my $msg = sprintf q{Controller %d [%s]: Storport driver '%s' is out of date},
1688               $nexus, $name, $stdr;
1689             report('storage', $msg, $E_WARNING, $nexus);
1690         }
1691         # Ok
1692         if ($status eq 'Ok' or ($status eq 'Non-Critical'
1693                                 and (defined $minfw or defined $mindr or defined $minstdr))) {
1694             my $msg = sprintf 'Controller %d [%s] is %s',
1695               $nexus, $name, $state;
1696             report('storage', $msg, $E_OK, $nexus);
1697         }
1698         # Default
1699         else {
1700             my $msg = sprintf 'Controller %d [%s] needs attention: %s',
1701               $nexus, $name, $state;
1702             report('storage', $msg, $status2nagios{$status}, $nexus);
1703         }
1704     }
1705     return;
1706 }
1707
1708
1709 #-----------------------------------------
1710 # STORAGE: Check physical drives
1711 #-----------------------------------------
1712 sub check_physical_disks {
1713     return if $#controllers == -1;
1714
1715     my $nexus    = undef;
1716     my $name     = undef;
1717     my $state    = undef;
1718     my $status   = undef;
1719     my $fpred    = undef;
1720     my $progr    = undef;
1721     my $ctrl     = undef;
1722     my $vendor   = undef;  # disk vendor
1723     my $product  = undef;  # product ID
1724     my $capacity = undef;  # disk length (size) in bytes
1725     my $media    = undef;  # media type (e.g. HDD, SSD)
1726     my $bus      = undef;  # bus protocol (e.g. SAS, SATA)
1727     my $spare    = undef;  # spare state (e.g. global hotspare)
1728     my $cert     = undef;  # if drive is certified or not
1729     my @output  = ();
1730
1731     if ($snmp) {
1732         my %pdisk_oid
1733           = (
1734              '1.3.6.1.4.1.674.10893.1.20.130.4.1.2'  => 'arrayDiskName',
1735              '1.3.6.1.4.1.674.10893.1.20.130.4.1.3'  => 'arrayDiskVendor',
1736              '1.3.6.1.4.1.674.10893.1.20.130.4.1.4'  => 'arrayDiskState',
1737              '1.3.6.1.4.1.674.10893.1.20.130.4.1.6'  => 'arrayDiskProductID',
1738              '1.3.6.1.4.1.674.10893.1.20.130.4.1.9'  => 'arrayDiskEnclosureID',
1739              '1.3.6.1.4.1.674.10893.1.20.130.4.1.10' => 'arrayDiskChannel',
1740              '1.3.6.1.4.1.674.10893.1.20.130.4.1.11' => 'arrayDiskLengthInMB',
1741              '1.3.6.1.4.1.674.10893.1.20.130.4.1.15' => 'arrayDiskTargetID',
1742              '1.3.6.1.4.1.674.10893.1.20.130.4.1.21' => 'arrayDiskBusType',
1743              '1.3.6.1.4.1.674.10893.1.20.130.4.1.22' => 'arrayDiskSpareState',
1744              '1.3.6.1.4.1.674.10893.1.20.130.4.1.24' => 'arrayDiskComponentStatus',
1745              '1.3.6.1.4.1.674.10893.1.20.130.4.1.26' => 'arrayDiskNexusID',
1746              '1.3.6.1.4.1.674.10893.1.20.130.4.1.31' => 'arrayDiskSmartAlertIndication',
1747              '1.3.6.1.4.1.674.10893.1.20.130.4.1.35' => 'arrayDiskMediaType',
1748              '1.3.6.1.4.1.674.10893.1.20.130.4.1.36' => 'arrayDiskDellCertified',
1749              '1.3.6.1.4.1.674.10893.1.20.130.5.1.7'  => 'arrayDiskEnclosureConnectionControllerNumber',
1750              '1.3.6.1.4.1.674.10893.1.20.130.6.1.7'  => 'arrayDiskChannelConnectionControllerNumber',
1751             );
1752         my $result = undef;
1753         if ($opt{use_get_table}) {
1754             my $arrayDiskTable = '1.3.6.1.4.1.674.10893.1.20.130.4';
1755             my $arrayDiskEnclosureConnectionControllerNumber = '1.3.6.1.4.1.674.10893.1.20.130.5.1.7';
1756             my $arrayDiskChannelConnectionControllerNumber = '1.3.6.1.4.1.674.10893.1.20.130.6.1.7';
1757
1758             $result  = $snmp_session->get_table(-baseoid => $arrayDiskTable);
1759             my $ext1 = $snmp_session->get_table(-baseoid => $arrayDiskEnclosureConnectionControllerNumber);
1760             my $ext2 = $snmp_session->get_table(-baseoid => $arrayDiskChannelConnectionControllerNumber);
1761
1762             if (defined $result) {
1763                 defined $ext1 && map { $$result{$_} = $$ext1{$_} } keys %{ $ext1 };
1764                 defined $ext2 && map { $$result{$_} = $$ext2{$_} } keys %{ $ext2 };
1765             }
1766         }
1767         else {
1768             $result = $snmp_session->get_entries(-columns => [keys %pdisk_oid]);
1769         }
1770
1771         if (!defined $result) {
1772             printf "SNMP ERROR [storage / pdisk]: %s.\n", $snmp_session->error;
1773             $snmp_session->close;
1774             exit $E_UNKNOWN;
1775         }
1776
1777         @output = @{ get_snmp_output($result, \%pdisk_oid) };
1778     }
1779     else {
1780         foreach my $c (@controllers) {
1781             # This blacklists disks with broken firmware, which includes
1782             # illegal XML characters that makes openmanage choke on itself
1783             next if blacklisted('ctrl_pdisk', $c);
1784
1785             push @output, @{ run_omreport("storage pdisk controller=$c") };
1786             map_item('ctrl', $c, \@output);
1787         }
1788     }
1789
1790     my %spare_state
1791       = (
1792          1  => 'VD member',    # disk is a member of a virtual disk
1793          2  => 'DG member',    # disk is a member of a disk group
1794          3  => 'Global HS',    # disk is a global hot spare
1795          4  => 'Dedicated HS', # disk is a dedicated hot spare
1796          5  => 'no',           # not a spare
1797          99 => 'n/a',          # not applicable
1798         );
1799
1800     my %media_type
1801       = (
1802          1 => 'unknown',
1803          2 => 'HDD',
1804          3 => 'SSD',
1805         );
1806
1807     my %bus_type
1808       = (
1809          1 => 'SCSI',
1810          2 => 'IDE',
1811          3 => 'Fibre Channel',
1812          4 => 'SSA',
1813          6 => 'USB',
1814          7 => 'SATA',
1815          8 => 'SAS',
1816         );
1817
1818     my %pdisk_state
1819       = (
1820          0  => 'Unknown',
1821          1  => 'Ready',
1822          2  => 'Failed',
1823          3  => 'Online',
1824          4  => 'Offline',
1825          6  => 'Degraded',
1826          7  => 'Recovering',
1827          11 => 'Removed',
1828          15 => 'Resynching',
1829          22 => 'Replacing', # FIXME: this one is not defined in the OMSA MIBs
1830          24 => 'Rebuilding',
1831          25 => 'No Media',
1832          26 => 'Formatting',
1833          28 => 'Diagnostics',
1834          34 => 'Predictive failure',
1835          35 => 'Initializing',
1836          39 => 'Foreign',
1837          40 => 'Clear',
1838          41 => 'Unsupported',
1839          53 => 'Incompatible',
1840         );
1841
1842     # Check physical disks on each of the controllers
1843   PDISK:
1844     foreach my $out (@output) {
1845         if ($snmp) {
1846             $name     = $out->{arrayDiskName} || 'Unknown disk';
1847             $state    = get_hashval($out->{arrayDiskState}, \%pdisk_state) || 'Unknown state';
1848             $status   = get_snmp_status($out->{arrayDiskComponentStatus});
1849             $fpred    = defined $out->{arrayDiskSmartAlertIndication}
1850               && $out->{arrayDiskSmartAlertIndication} == 2 ? 1 : 0;
1851             $progr    = q{};
1852             $nexus    = convert_nexus(($out->{arrayDiskNexusID} || 9999));
1853             $vendor   = $out->{arrayDiskVendor} || 'Unknown vendor';
1854             $product  = $out->{arrayDiskProductID} || 'Unknown product ID';
1855             $spare    = get_hashval($out->{arrayDiskSpareState}, \%spare_state) || q{};
1856             $bus      = get_hashval($out->{arrayDiskBusType}, \%bus_type);
1857             $media    = get_hashval($out->{arrayDiskMediaType}, \%media_type);
1858             $cert     = defined $out->{arrayDiskDellCertified} ? $out->{arrayDiskDellCertified} : 1;
1859             $capacity = exists $out->{arrayDiskLengthInMB}
1860               ? $out->{arrayDiskLengthInMB} * 1024**2 : -1;
1861
1862             # try to find the controller where the disk belongs
1863             if (exists $out->{arrayDiskEnclosureConnectionControllerNumber}) {
1864                 # for disks that are attached to an enclosure
1865                 $ctrl = $snmp_controller{$out->{arrayDiskEnclosureConnectionControllerNumber}};
1866             }
1867             elsif (exists $out->{arrayDiskChannelConnectionControllerNumber}) {
1868                 # for disks that are not attached to an enclosure
1869                 $ctrl = $snmp_controller{$out->{arrayDiskChannelConnectionControllerNumber}};
1870             }
1871             else {
1872                 # last resort... use the nexus id (old/broken hardware)
1873                 $ctrl = $nexus;
1874                 $ctrl =~ s{\A (\d+) : .* \z}{$1}xms;
1875             }
1876         }
1877         else {
1878             $name     = get_nonempty_string('Name', $out, 'Unknown disk');
1879             $state    = get_nonempty_string('State', $out, 'Unknown state');
1880             $status   = get_nonempty_string('Status', $out, 'Unknown');
1881             $fpred    = lc(get_nonempty_string('Failure Predicted', $out, q{})) eq 'yes' ? 1 : 0;
1882             $progr    = ' [' . get_nonempty_string('Progress', $out, q{}) . ']';
1883             $nexus    = join q{:}, $out->{ctrl}, $out->{'ID'};
1884             $vendor   = get_nonempty_string('Vendor ID', $out, 'Unknown Vendor');
1885             $product  = get_nonempty_string('Product ID', $out, 'Unknown Product ID');
1886             $media    = get_nonempty_string('Media', $out, undef);
1887             $bus      = get_nonempty_string('Bus Protocol', $out, undef);
1888             $spare    = get_nonempty_string('Hot Spare', $out, q{});
1889             $cert     = get_nonempty_string('Certified', $out, 1);
1890             $ctrl     = $out->{ctrl};
1891             $capacity = get_nonempty_string('Capacity', $out, q{});
1892             $capacity =~ s{\A .*? \((\d+) \s bytes\) \z}{$1}xms;
1893             if ($capacity eq 'Unavailable') {
1894                 $capacity = -1;
1895             }
1896             if ($cert eq 'Yes' or $cert eq 'Not Applicable') {
1897                 $cert = 1;
1898             }
1899             else {
1900                 $cert = 0;
1901             }
1902         }
1903
1904         $count{pdisk}++;
1905         next PDISK if blacklisted('pdisk', $nexus);
1906
1907         $vendor  =~ s{\s+\z}{}xms; # remove trailing whitespace
1908         $product =~ s{\s+\z}{}xms; # remove trailing whitespace
1909
1910         # If the disk is bad, the vendor field may be empty
1911         if ($vendor eq q{}) { $vendor = 'Unknown Vendor'; }
1912
1913         # Hot spare stuff
1914         if ($spare eq 'Global') { $spare = 'Global HS'; }
1915         elsif ($spare eq 'Dedicated') { $spare = 'Dedicated HS'; }
1916         elsif ($spare !~ m{\A Global|Dedicated}xms) { $spare = undef; }
1917
1918         # Calculate human readable capacity
1919         if ($capacity == -1) {
1920             # capacity is unknown
1921             $capacity = 'Unknown Size';
1922         }
1923         else {
1924             $capacity = ceil($capacity / 1000**3) >= 1000
1925               ? sprintf '%.1fTB', ($capacity / 1000**4)
1926                 : sprintf '%.0fGB', ($capacity / 1000**3);
1927             $capacity = '450GB' if $capacity eq '449GB';  # quick fix for 450GB disks
1928             $capacity = '300GB' if $capacity eq '299GB';  # quick fix for 300GB disks
1929             $capacity = '146GB' if $capacity eq '147GB';  # quick fix for 146GB disks
1930             $capacity = '100GB' if $capacity eq '99GB';   # quick fix for 100GB disks
1931         }
1932
1933         # Capitalize only the first letter of the vendor name
1934         $vendor = (substr $vendor, 0, 1) . lc (substr $vendor, 1, length $vendor);
1935
1936         # Remove unnecessary trademark rubbish from vendor name
1937         $vendor =~ s{\(tm\)\z}{}xms;
1938
1939         # bus and media aren't always defined
1940         my $busmedia = q{};
1941         if    (defined $bus && defined $media)   { $busmedia = "$bus-$media "; }
1942         elsif (defined $bus && ! defined $media) { $busmedia = "$bus ";        }
1943         elsif (! defined $bus && defined $media) { $busmedia = "$media ";      }
1944
1945         # Special case: Failure predicted
1946         if ($fpred) {
1947             my $msg = sprintf '%s [%s %s, %s] on ctrl %d needs attention: Failure Predicted',
1948               $name, $vendor, $product, $capacity, $ctrl;
1949             $msg .= " ($state)" if $state ne 'Predictive failure';
1950             report('storage', $msg,
1951                    ($status2nagios{$status} == $E_CRITICAL ? $E_CRITICAL : $E_WARNING), $nexus);
1952         }
1953         # Special case: Rebuilding / Replacing
1954         elsif ($state =~ m{\A Rebuilding|Replacing \z}xms) {
1955             my $msg = sprintf '%s [%s %s, %s] on ctrl %d is %s%s',
1956               $name, $vendor, $product, $capacity, $ctrl, $state, $progr;
1957             report('storage', $msg, $E_WARNING, $nexus);
1958         }
1959         # Special case: Uncertified disk
1960         elsif ($status eq 'Non-Critical' and !$cert) {
1961             if (blacklisted('pdisk_cert', $nexus)) {
1962                 my $msg = sprintf '%s [%s %s, %s] on ctrl %d is %s, Not Certified',
1963                   $name, $vendor, $product, $capacity, $ctrl, $state;
1964                 report('storage', $msg, $E_OK, $nexus);
1965             }
1966             else {
1967                 my $msg = sprintf '%s [%s %s, %s] on ctrl %d is Not Certified',
1968                   $name, $vendor, $product, $capacity, $ctrl;
1969                 report('storage', $msg, $E_WARNING, $nexus);
1970             }
1971         }
1972         # Default
1973         elsif ($status ne 'Ok') {
1974             my $msg =  sprintf '%s [%s %s, %s] on ctrl %d needs attention: %s',
1975               $name, $vendor, $product, $capacity, $ctrl, $state;
1976             report('storage', $msg, $status2nagios{$status}, $nexus);
1977         }
1978         # Ok
1979         else {
1980             my $msg = sprintf '%s [%s%s] on ctrl %d is %s',
1981               $name, $busmedia, $capacity, $ctrl, $state;
1982             if (defined $spare) { $msg .= " ($spare)"; }
1983             report('storage', $msg, $E_OK, $nexus);
1984         }
1985     }
1986     return;
1987 }
1988
1989
1990 #-----------------------------------------
1991 # STORAGE: Check logical drives
1992 #-----------------------------------------
1993 sub check_virtual_disks {
1994     return if $#controllers == -1;
1995
1996     my $name   = undef;
1997     my $nexus  = undef;
1998     my $dev    = undef;
1999     my $state  = undef;
2000     my $status = undef;
2001     my $layout = undef;
2002     my $size   = undef;
2003     my $progr  = undef;
2004     my $ctrl   = undef;
2005     my @output = ();
2006
2007     if ($snmp) {
2008         my %vdisk_oid
2009           = (
2010              '1.3.6.1.4.1.674.10893.1.20.140.1.1.3'  => 'virtualDiskDeviceName',
2011              '1.3.6.1.4.1.674.10893.1.20.140.1.1.4'  => 'virtualDiskState',
2012              '1.3.6.1.4.1.674.10893.1.20.140.1.1.6'  => 'virtualDiskLengthInMB',
2013              '1.3.6.1.4.1.674.10893.1.20.140.1.1.13' => 'virtualDiskLayout',
2014              '1.3.6.1.4.1.674.10893.1.20.140.1.1.20' => 'virtualDiskComponentStatus',
2015              '1.3.6.1.4.1.674.10893.1.20.140.1.1.21' => 'virtualDiskNexusID',
2016             );
2017         my $result = undef;
2018         if ($opt{use_get_table}) {
2019             my $virtualDiskTable = '1.3.6.1.4.1.674.10893.1.20.140.1';
2020             $result = $snmp_session->get_table(-baseoid => $virtualDiskTable);
2021         }
2022         else {
2023             $result = $snmp_session->get_entries(-columns => [keys %vdisk_oid]);
2024         }
2025
2026         # No logical drives is OK
2027         return if !defined $result;
2028
2029         @output = @{ get_snmp_output($result, \%vdisk_oid) };
2030     }
2031     else {
2032         foreach my $c (@controllers) {
2033             push @output, @{ run_omreport("storage vdisk controller=$c") };
2034             map_item('ctrl', $c, \@output);
2035         }
2036     }
2037
2038     my %vdisk_state
2039       = (
2040          0  => 'Unknown',
2041          1  => 'Ready',
2042          2  => 'Failed',
2043          3  => 'Online',
2044          4  => 'Offline',
2045          6  => 'Degraded',
2046          15 => 'Resynching',
2047          16 => 'Regenerating',
2048          24 => 'Rebuilding',
2049          26 => 'Formatting',
2050          32 => 'Reconstructing',
2051          35 => 'Initializing',
2052          36 => 'Background Initialization',
2053          38 => 'Resynching Paused',
2054          52 => 'Permanently Degraded',
2055          54 => 'Degraded Redundancy',
2056         );
2057
2058     my %vdisk_layout
2059       = (
2060          1  => 'Concatenated',
2061          2  => 'RAID-0',
2062          3  => 'RAID-1',
2063          4  => 'UNSUPPORTED:raid-2',
2064          5  => 'UNSUPPORTED:raid-3',
2065          6  => 'UNSUPPORTED:raid-4',
2066          7  => 'RAID-5',
2067          8  => 'RAID-6',
2068          9  => 'UNSUPPORTED:raid-7',
2069          10 => 'RAID-10',
2070          11 => 'UNSUPPORTED:raid-30',
2071          12 => 'RAID-50',
2072          13 => 'UNSUPPORTED:addSpares',
2073          14 => 'UNSUPPORTED:deleteLogical',
2074          15 => 'UNSUPPORTED:transformLogical',
2075          18 => 'UNSUPPORTED:raid-0-plus-1',
2076          19 => 'Concatenated RAID-1',
2077          20 => 'UNSUPPORTED:concatRaid-5',
2078          21 => 'UNSUPPORTED:noRaid',
2079          22 => 'UNSUPPORTED:volume',
2080          23 => 'UNSUPPORTED:raidMorph',
2081          24 => 'RAID-60',
2082          25 => 'CacheCade',
2083         );
2084
2085     # Check virtual disks on each of the controllers
2086   VDISK:
2087     foreach my $out (@output) {
2088         if ($snmp) {
2089             $dev    = $out->{virtualDiskDeviceName} || 'Unknown device';
2090             $state  = get_hashval($out->{virtualDiskState}, \%vdisk_state) || 'Unknown state';
2091             $layout = get_hashval($out->{virtualDiskLayout}, \%vdisk_layout) || 'Unknown layout';
2092             $status = get_snmp_status($out->{virtualDiskComponentStatus});
2093             $size   = sprintf '%.2f GB', ($out->{virtualDiskLengthInMB} || 0) / 1024;
2094             $progr  = q{};  # not available via SNMP
2095             $nexus  = convert_nexus(($out->{virtualDiskNexusID} || 9999));
2096         }
2097         else {
2098             $dev    = get_nonempty_string('Device Name', $out, 'Unknown device');
2099             $state  = get_nonempty_string('State', $out, 'Unknown state');
2100             $status = get_nonempty_string('Status', $out, 'Unknown');
2101             $layout = get_nonempty_string('Layout', $out, 'Unknown layout');
2102             $size   = get_nonempty_string('Size', $out, 'Unavailable');
2103             $size   =~ s{\A (.*GB).* \z}{$1}xms;
2104             $progr  = ' [' . get_nonempty_string('Progress', $out, q{}) . ']';
2105             $ctrl   = $out->{ctrl};
2106             $nexus  = join q{:}, $ctrl, get_nonempty_string('ID', $out, '9999');
2107         }
2108
2109         $count{vdisk}++;
2110         next VDISK if blacklisted('vdisk', $nexus);
2111
2112         # The device name is undefined sometimes
2113         $dev = q{} if !defined $dev;
2114
2115         # Special case: Regenerating
2116         if ($state eq 'Regenerating') {
2117             my $msg = sprintf q{Logical Drive '%s' [%s, %s] is %s%s},
2118               $dev, $layout, $size, $state, $progr;
2119             report('storage', $msg, $E_WARNING, $nexus);
2120         }
2121         # Default
2122         elsif ($status ne 'Ok') {
2123             my $msg = sprintf q{Logical Drive '%s' [%s, %s] needs attention: %s},
2124               $dev, $layout, $size, $state;
2125             report('storage', $msg, $status2nagios{$status}, $nexus);
2126         }
2127         # Ok
2128         else {
2129             my $msg = sprintf q{Logical Drive '%s' [%s, %s] is %s},
2130               $dev, $layout, $size, $state;
2131             report('storage', $msg, $E_OK, $nexus);
2132         }
2133     }
2134     return;
2135 }
2136
2137
2138 #-----------------------------------------
2139 # STORAGE: Check cache batteries
2140 #-----------------------------------------
2141 sub check_cache_battery {
2142     return if $#controllers == -1;
2143
2144     my $id     = undef;
2145     my $nexus  = undef;
2146     my $state  = undef;
2147     my $status = undef;
2148     my $ctrl   = undef;
2149     my $learn  = undef; # learn state
2150     my $pred   = undef; # battery's ability to be charged
2151     my @output = ();
2152
2153     if ($snmp) {
2154         my %bat_oid
2155           = (
2156              '1.3.6.1.4.1.674.10893.1.20.130.15.1.4'  => 'batteryState',
2157              '1.3.6.1.4.1.674.10893.1.20.130.15.1.6'  => 'batteryComponentStatus',
2158              '1.3.6.1.4.1.674.10893.1.20.130.15.1.9'  => 'batteryNexusID',
2159              '1.3.6.1.4.1.674.10893.1.20.130.15.1.10' => 'batteryPredictedCapacity',
2160              '1.3.6.1.4.1.674.10893.1.20.130.15.1.12' => 'batteryLearnState',
2161              '1.3.6.1.4.1.674.10893.1.20.130.16.1.5'  => 'batteryConnectionControllerNumber',
2162             );
2163         my $result = undef;
2164         if ($opt{use_get_table}) {
2165             my $batteryTable = '1.3.6.1.4.1.674.10893.1.20.130.15';
2166             my $batteryConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.16';
2167
2168             $result = $snmp_session->get_table(-baseoid => $batteryTable);
2169             my $ext = $snmp_session->get_table(-baseoid => $batteryConnectionTable);
2170
2171             if (defined $result) {
2172                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2173             }
2174         }
2175         else {
2176             $result = $snmp_session->get_entries(-columns => [keys %bat_oid]);
2177         }
2178
2179         # No cache battery is OK
2180         return if !defined $result;
2181
2182         @output = @{ get_snmp_output($result, \%bat_oid) };
2183     }
2184     else {
2185         foreach my $c (@controllers) {
2186             push @output, @{ run_omreport("storage battery controller=$c") };
2187             map_item('ctrl', $c, \@output);
2188         }
2189     }
2190
2191     my %bat_state
2192       = (
2193          0  => 'Unknown',
2194          1  => 'Ready',
2195          2  => 'Failed',
2196          6  => 'Degraded',
2197          7  => 'Reconditioning',
2198          9  => 'High',
2199          10 => 'Power Low',
2200          12 => 'Charging',
2201          21 => 'Missing',
2202          36 => 'Learning',
2203         );
2204
2205     # Specifies the learn state activity of the battery
2206     my %bat_learn_state
2207       = (
2208          1  => 'Failed',
2209          2  => 'Active',
2210          4  => 'Timed out',
2211          8  => 'Requested',
2212          16 => 'Idle',
2213         );
2214
2215     # This property displays the battery's ability to be charged
2216     my %bat_pred_cap
2217       = (
2218          1 => 'Failed',  # The battery cannot be charged and needs to be replaced
2219          2 => 'Ready',   # The battery can be charged to full capacity
2220          4 => 'Unknown', # The battery is completing a Learn cycle. The charge capacity of the
2221                          # battery cannot be determined until the Learn cycle is complete
2222         );
2223
2224     # Check battery on each of the controllers
2225   BATTERY:
2226     foreach my $out (@output) {
2227         if ($snmp) {
2228             $status = get_snmp_status($out->{batteryComponentStatus});
2229             $state  = get_hashval($out->{batteryState}, \%bat_state) || 'Unknown state';
2230             $learn  = get_hashval($out->{batteryLearnState}, \%bat_learn_state) || 'Unknown learn state';
2231             $pred   = get_hashval($out->{batteryPredictedCapacity}, \%bat_pred_cap) || 'Unknown predicted capacity status';
2232             $ctrl   = get_hashval($out->{batteryConnectionControllerNumber}, \%snmp_controller) || 9999;
2233             $nexus  = convert_nexus(($out->{batteryNexusID} || 9999));
2234             $id     = $nexus;
2235             $id     =~ s{\A \d+:(\d+) \z}{$1}xms;
2236         }
2237         else {
2238             $id     = get_nonempty_string('ID', $out, 9999);
2239             $state  = get_nonempty_string('State', $out, 'Unknown state');
2240             $status = get_nonempty_string('Status', $out, 'Unknown');
2241             $learn  = get_nonempty_string('Learn State', $out, 'Unknown learn state');
2242             $pred   = get_nonempty_string('Predicted Capacity Status', $out, 'Unknown predicted capacity status');
2243             $ctrl   = $out->{'ctrl'};
2244             $nexus  = join q{:}, $out->{ctrl}, $id;
2245         }
2246
2247         next BATTERY if blacklisted('bat', $nexus);
2248
2249         # Special case: Charging
2250         if ($state eq 'Charging') {
2251             if ($pred eq 'Failed') {
2252                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [replace battery]',
2253                   $id, $ctrl, $state, $pred;
2254                 report('storage', $msg, $E_CRITICAL, $nexus);
2255             }
2256             else {
2257                 next BATTERY if blacklisted('bat_charge', $nexus);
2258                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
2259                   $id, $ctrl, $state, $pred;
2260                 report('storage', $msg, $E_WARNING, $nexus);
2261             }
2262         }
2263         # Special case: Learning (battery learns its capacity)
2264         elsif ($state eq 'Learning') {
2265             if ($learn eq 'Failed') {
2266                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s)',
2267                   $id, $ctrl, $state, $learn;
2268                 report('storage', $msg, $E_CRITICAL, $nexus);
2269             }
2270             else {
2271                 next BATTERY if blacklisted('bat_charge', $nexus);
2272                 my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
2273                   $id, $ctrl, $state, $learn;
2274                 report('storage', $msg, $E_WARNING, $nexus);
2275             }
2276         }
2277         # Special case: Power Low (first part of recharge cycle)
2278         elsif ($state eq 'Power Low') {
2279             next BATTERY if blacklisted('bat_charge', $nexus);
2280             my $msg = sprintf 'Cache Battery %d in controller %d is %s [probably harmless]',
2281               $id, $ctrl, $state;
2282             report('storage', $msg, $E_WARNING, $nexus);
2283         }
2284         # Special case: Degraded and Non-Critical (usually part of recharge cycle)
2285         elsif ($state eq 'Degraded' && $status eq 'Non-Critical') {
2286             next BATTERY if blacklisted('bat_charge', $nexus);
2287             my $msg = sprintf 'Cache Battery %d in controller %d is %s (%s) [probably harmless]',
2288               $id, $ctrl, $state, $status;
2289             report('storage', $msg, $E_WARNING, $nexus);
2290         }
2291         # Default
2292         else {
2293             my $msg = sprintf 'Cache Battery %d in controller %d is %s',
2294               $id, $ctrl, $state;
2295             report('storage', $msg, $status2nagios{$status}, $nexus);
2296         }
2297     }
2298     return;
2299 }
2300
2301
2302 #-----------------------------------------
2303 # STORAGE: Check connectors (channels)
2304 #-----------------------------------------
2305 sub check_connectors {
2306     return if $#controllers == -1;
2307
2308     my $nexus  = undef;
2309     my $name   = undef;
2310     my $state  = undef;
2311     my $status = undef;
2312     my $type   = undef;
2313     my $ctrl   = undef;
2314     my @output = ();
2315
2316     if ($snmp) {
2317         my %conn_oid
2318           = (
2319              '1.3.6.1.4.1.674.10893.1.20.130.2.1.2'  => 'channelName',
2320              '1.3.6.1.4.1.674.10893.1.20.130.2.1.3'  => 'channelState',
2321              '1.3.6.1.4.1.674.10893.1.20.130.2.1.8'  => 'channelComponentStatus',
2322              '1.3.6.1.4.1.674.10893.1.20.130.2.1.9'  => 'channelNexusID',
2323              '1.3.6.1.4.1.674.10893.1.20.130.2.1.11' => 'channelBusType',
2324             );
2325         my $result = undef;
2326         if ($opt{use_get_table}) {
2327             my $channelTable = '1.3.6.1.4.1.674.10893.1.20.130.2';
2328             $result = $snmp_session->get_table(-baseoid => $channelTable);
2329         }
2330         else {
2331             $result = $snmp_session->get_entries(-columns => [keys %conn_oid]);
2332         }
2333
2334         if (!defined $result) {
2335             printf "SNMP ERROR [storage / channel]: %s.\n", $snmp_session->error;
2336             $snmp_session->close;
2337             exit $E_UNKNOWN;
2338         }
2339
2340         @output = @{ get_snmp_output($result, \%conn_oid) };
2341     }
2342     else {
2343         foreach my $c (@controllers) {
2344             push @output, @{ run_omreport("storage connector controller=$c") };
2345             map_item('ctrl', $c, \@output);
2346         }
2347     }
2348
2349     my %conn_state
2350       = (
2351          0 => 'Unknown',
2352          1 => 'Ready',
2353          2 => 'Failed',
2354          3 => 'Online',
2355          4 => 'Offline',
2356          6 => 'Degraded',
2357         );
2358
2359     my %conn_bustype
2360       = (
2361          1 => 'SCSI',
2362          2 => 'IDE',
2363          3 => 'Fibre Channel',
2364          4 => 'SSA',
2365          6 => 'USB',
2366          7 => 'SATA',
2367          8 => 'SAS',
2368         );
2369
2370     # Check connectors on each of the controllers
2371   CHANNEL:
2372     foreach my $out (@output) {
2373         if ($snmp) {
2374             $name   = $out->{channelName} || 'Unknown channel';
2375             $status = get_snmp_status($out->{channelComponentStatus});
2376             $state  = get_hashval($out->{channelState}, \%conn_state) || 'Unknown state';
2377             $type   = get_hashval($out->{channelBusType}, \%conn_bustype) || 'Unknown type';
2378             $nexus  = convert_nexus(($out->{channelNexusID} || 9999));
2379             $ctrl   = $nexus;
2380             $ctrl   =~ s{(\d+):\d+}{$1}xms;
2381         }
2382         else {
2383             $name   = get_nonempty_string('Name', $out, 'Unknown channel');
2384             $state  = get_nonempty_string('State', $out, 'Unknown state');
2385             $status = get_nonempty_string('Status', $out, 'Unknown');
2386             $type   = get_nonempty_string('Connector Type', $out, 'Unknown type');
2387             $ctrl   = $out->{ctrl};
2388             $nexus  = join q{:}, $out->{ctrl}, $out->{'ID'};
2389         }
2390
2391         next CHANNEL if blacklisted('conn', $nexus);
2392
2393         my $msg = sprintf '%s [%s] on controller %d is %s',
2394           $name, $type, $ctrl, $state;
2395         report('storage', $msg, $status2nagios{$status}, $nexus);
2396     }
2397     return;
2398 }
2399
2400
2401 #-----------------------------------------
2402 # STORAGE: Check enclosures
2403 #-----------------------------------------
2404 sub check_enclosures {
2405     my $id       = undef;
2406     my $nexus    = undef;
2407     my $name     = undef;
2408     my $state    = undef;
2409     my $status   = undef;
2410     my $firmware = undef;
2411     my $ctrl     = undef;
2412     my $occupied_slots = undef; # number of occupied slots
2413     my $total_slots    = undef; # number of total slots
2414     my @output   = ();
2415
2416     if ($snmp) {
2417         my %encl_oid
2418           = (
2419              '1.3.6.1.4.1.674.10893.1.20.130.3.1.1'  => 'enclosureNumber',
2420              '1.3.6.1.4.1.674.10893.1.20.130.3.1.2'  => 'enclosureName',
2421              '1.3.6.1.4.1.674.10893.1.20.130.3.1.4'  => 'enclosureState',
2422              '1.3.6.1.4.1.674.10893.1.20.130.3.1.19' => 'enclosureChannelNumber',
2423              '1.3.6.1.4.1.674.10893.1.20.130.3.1.24' => 'enclosureComponentStatus',
2424              '1.3.6.1.4.1.674.10893.1.20.130.3.1.25' => 'enclosureNexusID',
2425              '1.3.6.1.4.1.674.10893.1.20.130.3.1.26' => 'enclosureFirmwareVersion',
2426              '1.3.6.1.4.1.674.10893.1.20.130.3.1.31' => 'enclosureOccupiedSlotCount', # new in OMSA 6.3.0
2427              '1.3.6.1.4.1.674.10893.1.20.130.3.1.32' => 'enclosureTotalSlots', # new in OMSA 6.3.0
2428             );
2429         my $result = undef;
2430         if ($opt{use_get_table}) {
2431             my $enclosureTable = '1.3.6.1.4.1.674.10893.1.20.130.3';
2432             $result = $snmp_session->get_table(-baseoid => $enclosureTable);
2433         }
2434         else {
2435             $result = $snmp_session->get_entries(-columns => [keys %encl_oid]);
2436         }
2437
2438         # No enclosures is OK
2439         return if !defined $result;
2440
2441         @output = @{ get_snmp_output($result, \%encl_oid) };
2442     }
2443     else {
2444         foreach my $c (@controllers) {
2445             push @output, @{ run_omreport("storage enclosure controller=$c") };
2446             map_item('ctrl', $c, \@output);
2447         }
2448     }
2449
2450     my %encl_state
2451       = (
2452          0 => 'Unknown',
2453          1 => 'Ready',
2454          2 => 'Failed',
2455          3 => 'Online',
2456          4 => 'Offline',
2457          6 => 'Degraded',
2458         );
2459
2460   ENCLOSURE:
2461     foreach my $out (@output) {
2462         if ($snmp) {
2463             $id       = ($out->{enclosureNumber} || 10000) - 1;
2464             $name     = $out->{enclosureName} || 'Unknown enclosure';
2465             $state    = get_hashval($out->{enclosureState}, \%encl_state) || 'Unknown state';
2466             $status   = get_snmp_status($out->{enclosureComponentStatus});
2467             $firmware = $out->{enclosureFirmwareVersion} || 'N/A';
2468             $nexus    = convert_nexus(($out->{enclosureNexusID} || 9999));
2469             $ctrl     = $nexus;
2470             $ctrl     =~ s{\A (\d+):.* \z}{$1}xms;
2471             # for the next two, a value of 9999 means feature not available
2472             $occupied_slots = defined $out->{enclosureOccupiedSlotCount}
2473               && $out->{enclosureOccupiedSlotCount} != 9999
2474                 ? $out->{enclosureOccupiedSlotCount} : undef;
2475             $total_slots    = defined $out->{enclosureTotalSlots}
2476               && $out->{enclosureTotalSlots} != 9999
2477                 ? $out->{enclosureTotalSlots} : undef;
2478         }
2479         else {
2480             $id       = get_nonempty_string('ID', $out, 9999);
2481             $name     = get_nonempty_string('Name', $out, 'Unknown enclosure');
2482             $state    = get_nonempty_string('State', $out, 'Unknown state');
2483             $status   = get_nonempty_string('Status', $out, 'Unknown');
2484             $firmware = get_nonempty_string('Firmware Version', $out, 'N/A');
2485             $firmware =~ s{Not\sApplicable}{N/A}xms;
2486             $nexus    = join q{:}, $out->{ctrl}, $id;
2487             $ctrl     = $out->{ctrl};
2488         }
2489
2490         $name     =~ s{\s+\z}{}xms; # remove trailing whitespace
2491         $firmware =~ s{\s+\z}{}xms; # remove trailing whitespace
2492
2493         # store enclosure data for future use
2494         if ($snmp) {
2495             $snmp_enclosure{$out->{enclosureNumber}}{id}    = $id;
2496             $snmp_enclosure{$out->{enclosureNumber}}{name}  = $name;
2497             $snmp_enclosure{$out->{enclosureNumber}}{nexus} = $nexus;
2498         }
2499         else {
2500             push @enclosures, { 'id'    => $id,
2501                                 'ctrl'  => $out->{ctrl},
2502                                 'name'  => $name };
2503         }
2504
2505         # Collecting some storage info
2506         $sysinfo{'enclosure'}{$nexus}{'id'}       = $nexus;
2507         $sysinfo{'enclosure'}{$nexus}{'name'}     = $name;
2508         $sysinfo{'enclosure'}{$nexus}{'firmware'} = $firmware;
2509
2510         next ENCLOSURE if blacklisted('encl', $nexus);
2511
2512         my $msg = q{};
2513         if (defined $occupied_slots && defined $total_slots) {
2514             $msg = sprintf 'Enclosure %s [%s, %d/%d slots occupied] on ctrl %d is %s',
2515               $nexus, $name, $occupied_slots, $total_slots, $ctrl, $state;
2516         }
2517         else {
2518             $msg = sprintf 'Enclosure %s [%s] on controller %d is %s',
2519               $nexus, $name, $ctrl, $state;
2520         }
2521         report('storage', $msg, $status2nagios{$status}, $nexus);
2522     }
2523     return;
2524 }
2525
2526
2527 #-----------------------------------------
2528 # STORAGE: Check enclosure fans
2529 #-----------------------------------------
2530 sub check_enclosure_fans {
2531     return if $#controllers == -1;
2532
2533     my $nexus     = undef;
2534     my $name      = undef;
2535     my $state     = undef;
2536     my $status    = undef;
2537     my $speed     = undef;
2538     my $encl_id   = undef;
2539     my $encl_name = undef;
2540     my @output    = ();
2541
2542     if ($snmp) {
2543         my %fan_oid
2544           = (
2545              '1.3.6.1.4.1.674.10893.1.20.130.7.1.2'  => 'fanName',
2546              '1.3.6.1.4.1.674.10893.1.20.130.7.1.4'  => 'fanState',
2547              '1.3.6.1.4.1.674.10893.1.20.130.7.1.11' => 'fanProbeCurrValue',
2548              '1.3.6.1.4.1.674.10893.1.20.130.7.1.15' => 'fanComponentStatus',
2549              '1.3.6.1.4.1.674.10893.1.20.130.7.1.16' => 'fanNexusID',
2550              '1.3.6.1.4.1.674.10893.1.20.130.8.1.4'  => 'fanConnectionEnclosureName',
2551              '1.3.6.1.4.1.674.10893.1.20.130.8.1.5'  => 'fanConnectionEnclosureNumber',
2552             );
2553         my $result = undef;
2554         if ($opt{use_get_table}) {
2555             my $fanTable = '1.3.6.1.4.1.674.10893.1.20.130.7';
2556             my $fanConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.8';
2557
2558             $result = $snmp_session->get_table(-baseoid => $fanTable);
2559             my $ext = $snmp_session->get_table(-baseoid => $fanConnectionTable);
2560
2561             if (defined $result) {
2562                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2563             }
2564         }
2565         else {
2566             $result = $snmp_session->get_entries(-columns => [keys %fan_oid]);
2567         }
2568
2569         # No enclosure fans is OK
2570         return if !defined $result;
2571
2572         @output = @{ get_snmp_output($result, \%fan_oid) };
2573     }
2574     else {
2575         foreach my $enc (@enclosures) {
2576             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=fans") };
2577             map_item('ctrl', $enc->{ctrl}, \@output);
2578             map_item('encl_id', $enc->{id}, \@output);
2579             map_item('encl_name', $enc->{name}, \@output);
2580         }
2581     }
2582
2583     my %fan_state
2584       = (
2585          0  => 'Unknown',
2586          1  => 'Ready',
2587          2  => 'Failed',
2588          3  => 'Online',
2589          4  => 'Offline',
2590          6  => 'Degraded',
2591          21 => 'Missing',
2592         );
2593
2594     # Check fans on each of the enclosures
2595   FAN:
2596     foreach my $out (@output) {
2597         if ($snmp) {
2598             $name      = $out->{fanName} || 'Unknown fan';
2599             $state     = get_hashval($out->{fanState}, \%fan_state) || 'Unknown state';
2600             $status    = get_snmp_status($out->{fanComponentStatus});
2601             $speed     = $out->{fanProbeCurrValue} || 'N/A';
2602             $encl_name = $out->{fanConnectionEnclosureName} || 'Unknown enclosure';
2603             $encl_id   = $snmp_enclosure{$out->{fanConnectionEnclosureNumber}}{nexus};
2604             $nexus     = convert_nexus(($out->{fanNexusID} || 9999));
2605         }
2606         else {
2607             $name      = get_nonempty_string('Name', $out, 'Unknown fan');
2608             $state     = get_nonempty_string('State', $out, 'Unknown state');
2609             $status    = get_nonempty_string('Status', $out, 'Unknown');
2610             $speed     = get_nonempty_string('Speed', $out, 'N/A');
2611             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2612             $encl_name = $out->{encl_name};
2613             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, get_nonempty_string('ID', $out, '9999');
2614         }
2615
2616         next FAN if blacklisted('encl_fan', $nexus);
2617
2618         # Default
2619         if ($status ne 'Ok') {
2620             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
2621               $name, $encl_id, $encl_name, $state;
2622             report('storage', $msg, $status2nagios{$status}, $nexus);
2623         }
2624         # Ok
2625         else {
2626             my $msg = sprintf '%s in enclosure %s [%s] is %s (speed=%s)',
2627               $name, $encl_id, $encl_name, $state, $speed;
2628             report('storage', $msg, $E_OK, $nexus);
2629         }
2630     }
2631     return;
2632 }
2633
2634
2635 #-----------------------------------------
2636 # STORAGE: Check enclosure power supplies
2637 #-----------------------------------------
2638 sub check_enclosure_pwr {
2639     return if $#controllers == -1;
2640
2641     my $nexus     = undef;
2642     my $name      = undef;
2643     my $state     = undef;
2644     my $status    = undef;
2645     my $encl_id   = undef;
2646     my $encl_name = undef;
2647     my @output    = ();
2648
2649     if ($snmp) {
2650         my %ps_oid
2651           = (
2652              '1.3.6.1.4.1.674.10893.1.20.130.9.1.2'  => 'powerSupplyName',
2653              '1.3.6.1.4.1.674.10893.1.20.130.9.1.4'  => 'powerSupplyState',
2654              '1.3.6.1.4.1.674.10893.1.20.130.9.1.9'  => 'powerSupplyComponentStatus',
2655              '1.3.6.1.4.1.674.10893.1.20.130.9.1.10' => 'powerSupplyNexusID',
2656              '1.3.6.1.4.1.674.10893.1.20.130.10.1.4' => 'powerSupplyConnectionEnclosureName',
2657              '1.3.6.1.4.1.674.10893.1.20.130.10.1.5' => 'powerSupplyConnectionEnclosureNumber',
2658             );
2659         my $result = undef;
2660         if ($opt{use_get_table}) {
2661             my $powerSupplyTable = '1.3.6.1.4.1.674.10893.1.20.130.9';
2662             my $powerSupplyConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.10';
2663
2664             $result = $snmp_session->get_table(-baseoid => $powerSupplyTable);
2665             my $ext = $snmp_session->get_table(-baseoid => $powerSupplyConnectionTable);
2666
2667             if (defined $result) {
2668                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2669             }
2670         }
2671         else {
2672             $result = $snmp_session->get_entries(-columns => [keys %ps_oid]);
2673         }
2674
2675         # No enclosure power supplies is OK
2676         return if !defined $result;
2677
2678         @output = @{ get_snmp_output($result, \%ps_oid) };
2679     }
2680     else {
2681         foreach my $enc (@enclosures) {
2682             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=pwrsupplies") };
2683             map_item('ctrl', $enc->{ctrl}, \@output);
2684             map_item('encl_id', $enc->{id}, \@output);
2685             map_item('encl_name', $enc->{name}, \@output);
2686         }
2687     }
2688
2689     my %ps_state
2690       = (
2691          0  => 'Unknown',
2692          1  => 'Ready',
2693          2  => 'Failed',
2694          5  => 'Not Installed',
2695          6  => 'Degraded',
2696          11 => 'Removed',
2697          21 => 'Missing',
2698         );
2699
2700     # Check power supplies on each of the enclosures
2701   PS:
2702     foreach my $out (@output) {
2703         if ($snmp) {
2704             $name      = $out->{powerSupplyName} || 'Unknown PSU';
2705             $state     = get_hashval($out->{powerSupplyState}, \%ps_state) || 'Unknown state';
2706             $status    = get_snmp_status($out->{powerSupplyComponentStatus});
2707             $encl_id   = $snmp_enclosure{$out->{powerSupplyConnectionEnclosureNumber}}{nexus};
2708             $encl_name = $out->{powerSupplyConnectionEnclosureName} || 'Unknown enclosure';
2709             $nexus     = convert_nexus(($out->{powerSupplyNexusID} || 9999));
2710         }
2711         else {
2712             $name      = get_nonempty_string('Name', $out, 'Unknown PSU');
2713             $state     = get_nonempty_string('State', $out, 'Unknown state');
2714             $status    = get_nonempty_string('Status', $out, 'Unknown');
2715             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2716             $encl_name = $out->{encl_name};
2717             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, get_nonempty_string('ID', $out, '9999');
2718         }
2719
2720         next PS if blacklisted('encl_ps', $nexus);
2721
2722         # Default
2723         if ($status ne 'Ok') {
2724             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
2725               $name, $encl_id, $encl_name, $state;
2726             report('storage', $msg, $status2nagios{$status}, $nexus);
2727         }
2728         # Ok
2729         else {
2730             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2731               $name, $encl_id, $encl_name, $state;
2732             report('storage', $msg, $E_OK, $nexus);
2733         }
2734     }
2735     return;
2736 }
2737
2738
2739 #-----------------------------------------
2740 # STORAGE: Check enclosure temperatures
2741 #-----------------------------------------
2742 sub check_enclosure_temp {
2743     return if $#controllers == -1;
2744
2745     my $nexus     = undef;
2746     my $name      = undef;
2747     my $state     = undef;
2748     my $status    = undef;
2749     my $reading   = undef;
2750     my $unit      = undef;
2751     my $max_warn  = undef;
2752     my $max_crit  = undef;
2753     my $min_warn  = undef;
2754     my $min_crit  = undef;
2755     my $encl_id   = undef;
2756     my $encl_name = undef;
2757     my @output    = ();
2758
2759     if ($snmp) {
2760         my %temp_oid
2761           = (
2762              '1.3.6.1.4.1.674.10893.1.20.130.11.1.2'  => 'temperatureProbeName',
2763              '1.3.6.1.4.1.674.10893.1.20.130.11.1.4'  => 'temperatureProbeState',
2764              '1.3.6.1.4.1.674.10893.1.20.130.11.1.6'  => 'temperatureProbeUnit',
2765              '1.3.6.1.4.1.674.10893.1.20.130.11.1.7'  => 'temperatureProbeMinWarning',
2766              '1.3.6.1.4.1.674.10893.1.20.130.11.1.8'  => 'temperatureProbeMinCritical',
2767              '1.3.6.1.4.1.674.10893.1.20.130.11.1.9'  => 'temperatureProbeMaxWarning',
2768              '1.3.6.1.4.1.674.10893.1.20.130.11.1.10' => 'temperatureProbeMaxCritical',
2769              '1.3.6.1.4.1.674.10893.1.20.130.11.1.11' => 'temperatureProbeCurValue',
2770              '1.3.6.1.4.1.674.10893.1.20.130.11.1.13' => 'temperatureProbeComponentStatus',
2771              '1.3.6.1.4.1.674.10893.1.20.130.11.1.14' => 'temperatureProbeNexusID',
2772              '1.3.6.1.4.1.674.10893.1.20.130.12.1.4'  => 'temperatureConnectionEnclosureName',
2773              '1.3.6.1.4.1.674.10893.1.20.130.12.1.5'  => 'temperatureConnectionEnclosureNumber',
2774             );
2775         my $result = undef;
2776         if ($opt{use_get_table}) {
2777             my $temperatureProbeTable = '1.3.6.1.4.1.674.10893.1.20.130.11';
2778             my $temperatureConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.12';
2779
2780             $result = $snmp_session->get_table(-baseoid => $temperatureProbeTable);
2781             my $ext = $snmp_session->get_table(-baseoid => $temperatureConnectionTable);
2782
2783             if (defined $result) {
2784                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2785             }
2786         }
2787         else {
2788             $result = $snmp_session->get_entries(-columns => [keys %temp_oid]);
2789         }
2790
2791         # No enclosure temperature probes is OK
2792         return if !defined $result;
2793
2794         @output = @{ get_snmp_output($result, \%temp_oid) };
2795     }
2796     else {
2797         foreach my $enc (@enclosures) {
2798             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=temps") };
2799             map_item('ctrl', $enc->{ctrl}, \@output);
2800             map_item('encl_id', $enc->{id}, \@output);
2801             map_item('encl_name', $enc->{name}, \@output);
2802         }
2803     }
2804
2805     my %temp_state
2806       = (
2807          0  => 'Unknown',
2808          1  => 'Ready',
2809          2  => 'Failed',
2810          4  => 'Offline',
2811          6  => 'Degraded',
2812          9  => 'Inactive',
2813          21 => 'Missing',
2814         );
2815
2816     # Check temperature probes on each of the enclosures
2817   TEMP:
2818     foreach my $out (@output) {
2819         if ($snmp) {
2820             $name      = $out->{temperatureProbeName} || 'Unknown temp probe';
2821             $state     = get_hashval($out->{temperatureProbeState}, \%temp_state) || 'Unknown state';
2822             $status    = get_snmp_probestatus($out->{temperatureProbeComponentStatus});
2823             $unit      = $out->{temperatureProbeUnit} || 'Unknown unit';
2824             $reading   = $out->{temperatureProbeCurValue} || '[N/A]';
2825             $max_warn  = $out->{temperatureProbeMaxWarning} || '[N/A]';
2826             $max_crit  = $out->{temperatureProbeMaxCritical} || '[N/A]';
2827             $min_warn  = $out->{temperatureProbeMinWarning} || '[N/A]';
2828             $min_crit  = $out->{temperatureProbeMinCritical} || '[N/A]';
2829             $encl_id   = $snmp_enclosure{$out->{temperatureConnectionEnclosureNumber}}{nexus};
2830             $encl_name = $out->{temperatureConnectionEnclosureName} || 'Unknown enclosure';
2831             $nexus     = convert_nexus(($out->{temperatureProbeNexusID} || 9999));
2832         }
2833         else {
2834             $name      = get_nonempty_string('Name', $out, 'Unknown temp probe');
2835             $state     = get_nonempty_string('State', $out, 'Unknown state');
2836             $status    = get_nonempty_string('Status', $out, 'Unknown');
2837             $unit      = 'FIXME';
2838             $reading   = get_nonempty_string('Reading', $out, '[N/A]');
2839             $max_warn  = get_nonempty_string('Maximum Warning Threshold', $out, '[N/A]');
2840             $max_crit  = get_nonempty_string('Maximum Failure Threshold', $out, '[N/A]');
2841             $min_warn  = get_nonempty_string('Minimum Warning Threshold', $out, '[N/A]');
2842             $min_crit  = get_nonempty_string('Minimum Failure Threshold', $out, '[N/A]');
2843             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
2844             $encl_name = $out->{encl_name};
2845             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, get_nonempty_string('ID', $out, '9999');
2846         }
2847
2848         next TEMP if blacklisted('encl_temp', $nexus);
2849
2850         # Make sure these values are integers
2851         $reading  =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $reading  = '[N/A]';
2852         $max_warn =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $max_warn = '[N/A]';
2853         $max_crit =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $max_crit = '[N/A]';
2854         $min_warn =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $min_warn = '[N/A]';
2855         $min_crit =~ s{\A \s* (-?\d+) \s* C? \s* \z}{$1}xms or $min_crit = '[N/A]';
2856
2857         # Convert temp units
2858         if ($opt{tempunit} ne 'C') {
2859             $reading  = temp_from_celsius($reading,  $opt{tempunit});
2860             $max_warn = temp_from_celsius($max_warn, $opt{tempunit});
2861             $max_crit = temp_from_celsius($max_crit, $opt{tempunit});
2862             $min_warn = temp_from_celsius($min_warn, $opt{tempunit});
2863             $min_crit = temp_from_celsius($min_crit, $opt{tempunit});
2864         }
2865
2866         # Inactive temp probes
2867         if ($status eq 'Unknown' and $state eq 'Inactive') {
2868             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2869               $name, $encl_id, $encl_name, $state;
2870             report('storage', $msg, $E_OK, $nexus);
2871         }
2872         elsif ($status ne 'Ok' and $max_crit ne '[N/A]' and $reading > $max_crit) {
2873             my $msg = sprintf '%s in enclosure %s [%s] is critically high at %s %s',
2874               $name, $encl_id, $encl_name, $reading, $opt{tempunit};
2875             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2876             report('chassis', $msg, $err, $nexus);
2877         }
2878         elsif ($status ne 'Ok' and $max_warn ne '[N/A]' and $reading > $max_warn) {
2879             my $msg = sprintf '%s in enclosure %s [%s] is too high at %s %s',
2880               $name, $encl_id, $encl_name, $reading, $opt{tempunit};
2881             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2882             report('chassis', $msg, $err, $nexus);
2883         }
2884         elsif ($status ne 'Ok' and $min_crit ne '[N/A]' and $reading < $min_crit) {
2885             my $msg = sprintf '%s in enclosure %s [%s] is critically low at %s %s',
2886               $name, $encl_id, $encl_name, $reading, $opt{tempunit};
2887             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2888             report('chassis', $msg, $err, $nexus);
2889         }
2890         elsif ($status ne 'Ok' and $min_warn ne '[N/A]' and $reading < $min_warn) {
2891             my $msg = sprintf '%s in enclosure %s [%s] is too low at %s %s',
2892               $name, $encl_id, $encl_name, $reading, $opt{tempunit};
2893             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2894             report('chassis', $msg, $err, $nexus);
2895         }
2896         # Default
2897         elsif ($status ne 'Ok') {
2898             my $msg = sprintf '%s in enclosure %s [%s] is %s',
2899               $name, $encl_id, $encl_name, $state;
2900             if (defined $reading && $reading =~ m{\A -?\d+ \z}xms) {
2901                 # take into account that with certain states the
2902                 # reading doesn't exist or is not an integer
2903                 $msg .= sprintf ' at %s %s', $reading, $opt{tempunit};
2904                 if ($min_warn eq '[N/A]' or $min_crit eq '[N/A]') {
2905                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
2906                 }
2907                 else {
2908                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
2909                       $min_warn, $min_crit, $max_warn, $max_crit;
2910                 }
2911             }
2912             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
2913             report('storage', $msg, $err, $nexus);
2914         }
2915         # Ok
2916         else {
2917             my $msg = sprintf '%s in enclosure %s [%s]',
2918               $name, $encl_id, $encl_name;
2919             if (defined $reading && $reading ne '[N/A]') {
2920                 # take into account that with certain states the
2921                 # reading doesn't exist or is not an integer
2922                 $msg .= sprintf ' reads %s %s', $reading, $opt{tempunit};
2923                 if ($min_warn eq '[N/A]' or $min_crit eq '[N/A]') {
2924                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
2925                 }
2926                 else {
2927                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
2928                       $min_warn, $min_crit, $max_warn, $max_crit;
2929                 }
2930             }
2931             else {
2932                 $msg .= sprintf ' is %s', $state;
2933             }
2934             report('storage', $msg, $E_OK, $nexus);
2935         }
2936
2937         # Collect performance data
2938         if (defined $opt{perfdata} && $reading ne '[N/A]') {
2939             my $index = $name;
2940             $index =~ s{\A Temperature\sProbe\s(\d+) \z}{$1}gxms;
2941             my $legacy_name = $name;
2942             $legacy_name =~ s{\A Temperature\sProbe\s(\d+) \z}{temp_$1}gxms;
2943             my $legacy_label = lc "enclosure_${encl_id}_${legacy_name}";
2944             my $legacy_mini = $legacy_label;
2945             $legacy_mini =~ s{enclosure_(.+?)_temp_(.+?)}{e$1t$2}xms;
2946             push @perfdata, {
2947                              type   => 'E',
2948                              id     => $opt{perfdata} eq 'minimal' ? "${encl_id}_t${index}" : "${encl_id}_temp_${index}",
2949                              unit   => $opt{tempunit},
2950                              label  => q{},
2951                              legacy => $legacy_label,
2952                              mini   => $legacy_mini,
2953                              value  => $reading,
2954                              warn   => $max_warn,
2955                              crit   => $max_crit,
2956                             };
2957         }
2958     }
2959     return;
2960 }
2961
2962
2963 #-----------------------------------------
2964 # STORAGE: Check enclosure management modules (EMM)
2965 #-----------------------------------------
2966 sub check_enclosure_emms {
2967     return if $#controllers == -1;
2968
2969     my $nexus     = undef;
2970     my $name      = undef;
2971     my $state     = undef;
2972     my $status    = undef;
2973     my $encl_id   = undef;
2974     my $encl_name = undef;
2975     my @output    = ();
2976
2977     if ($snmp) {
2978         my %emms_oid
2979           = (
2980              '1.3.6.1.4.1.674.10893.1.20.130.13.1.2'  => 'enclosureManagementModuleName',
2981              '1.3.6.1.4.1.674.10893.1.20.130.13.1.4'  => 'enclosureManagementModuleState',
2982              '1.3.6.1.4.1.674.10893.1.20.130.13.1.11' => 'enclosureManagementModuleComponentStatus',
2983              '1.3.6.1.4.1.674.10893.1.20.130.13.1.12' => 'enclosureManagementModuleNexusID',
2984              '1.3.6.1.4.1.674.10893.1.20.130.14.1.4'  => 'enclosureManagementModuleConnectionEnclosureName',
2985              '1.3.6.1.4.1.674.10893.1.20.130.14.1.5'  => 'enclosureManagementModuleConnectionEnclosureNumber',
2986             );
2987         my $result = undef;
2988         if ($opt{use_get_table}) {
2989             my $enclosureManagementModuleTable = '1.3.6.1.4.1.674.10893.1.20.130.13';
2990             my $enclosureManagementModuleConnectionTable = '1.3.6.1.4.1.674.10893.1.20.130.14';
2991
2992             $result = $snmp_session->get_table(-baseoid => $enclosureManagementModuleTable);
2993             my $ext = $snmp_session->get_table(-baseoid => $enclosureManagementModuleConnectionTable);
2994
2995             if (defined $result) {
2996                 defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
2997             }
2998         }
2999         else {
3000             $result = $snmp_session->get_entries(-columns => [keys %emms_oid]);
3001         }
3002
3003         # No enclosure EMMs is OK
3004         return if !defined $result;
3005
3006         @output = @{ get_snmp_output($result, \%emms_oid) };
3007     }
3008     else {
3009         foreach my $enc (@enclosures) {
3010             push @output, @{ run_omreport("storage enclosure controller=$enc->{ctrl} enclosure=$enc->{id} info=emms") };
3011             map_item('ctrl', $enc->{ctrl}, \@output);
3012             map_item('encl_id', $enc->{id}, \@output);
3013             map_item('encl_name', $enc->{name}, \@output);
3014         }
3015     }
3016
3017     my %emms_state
3018       = (
3019          0  => 'Unknown',
3020          1  => 'Ready',
3021          2  => 'Failed',
3022          3  => 'Online',
3023          4  => 'Offline',
3024          5  => 'Not Installed',
3025          6  => 'Degraded',
3026          21 => 'Missing',
3027         );
3028
3029     # Check EMMs on each of the enclosures
3030   EMM:
3031     foreach my $out (@output) {
3032         if ($snmp) {
3033             $name      = $out->{enclosureManagementModuleName} || 'Unknown EMM';
3034             $state     = get_hashval($out->{enclosureManagementModuleState}, \%emms_state) || 'Unknown state';
3035             $status    = get_snmp_status($out->{enclosureManagementModuleComponentStatus});
3036             $encl_id   = $snmp_enclosure{$out->{enclosureManagementModuleConnectionEnclosureNumber}}{nexus};
3037             $encl_name = $out->{enclosureManagementModuleConnectionEnclosureName} || 'Unknown enclosure';
3038             $nexus     = convert_nexus(($out->{enclosureManagementModuleNexusID} || 9999));
3039         }
3040         else {
3041             $name      = get_nonempty_string('Name', $out, 'Unknown EMM');
3042             $state     = get_nonempty_string('State', $out, 'Unknown state');
3043             $status    = get_nonempty_string('Status', $out, 'Unknown');
3044             $encl_id   = join q{:}, $out->{ctrl}, $out->{'encl_id'};
3045             $encl_name = $out->{encl_name};
3046             $nexus     = join q{:}, $out->{ctrl}, $out->{'encl_id'}, get_nonempty_string('ID', $out, '9999');
3047         }
3048
3049         next EMM if blacklisted('encl_emm', $nexus);
3050
3051         # Not installed
3052         if ($status =~ m{\A Other|Unknown \z}xms and $state eq 'Not Installed') {
3053             my $msg = sprintf '%s in enclosure %s [%s] is %s',
3054               $name, $encl_id, $encl_name, $state;
3055             report('storage', $msg, $E_OK, $nexus);
3056         }
3057         # Default
3058         elsif ($status ne 'Ok') {
3059             my $msg = sprintf '%s in enclosure %s [%s] needs attention: %s',
3060               $name, $encl_id, $encl_name, $state;
3061             report('storage', $msg, $status2nagios{$status}, $nexus);
3062         }
3063         # Ok
3064         else {
3065             my $msg = sprintf '%s in enclosure %s [%s] is %s',
3066               $name, $encl_id, $encl_name, $state;
3067             report('storage', $msg, $E_OK, $nexus);
3068         }
3069     }
3070     return;
3071 }
3072
3073
3074 #-----------------------------------------
3075 # CHASSIS: Check memory modules
3076 #-----------------------------------------
3077 sub check_memory {
3078     my $index    = undef;
3079     my $status   = undef;
3080     my $location = undef;
3081     my $size     = undef;
3082     my $modes    = undef;
3083     my @failures = ();
3084     my @output   = ();
3085
3086     if ($snmp) {
3087         my %dimm_oid
3088           = (
3089              '1.3.6.1.4.1.674.10892.1.1100.50.1.2.1'  => 'memoryDeviceIndex',
3090              '1.3.6.1.4.1.674.10892.1.1100.50.1.5.1'  => 'memoryDeviceStatus',
3091              '1.3.6.1.4.1.674.10892.1.1100.50.1.8.1'  => 'memoryDeviceLocationName',
3092              '1.3.6.1.4.1.674.10892.1.1100.50.1.14.1' => 'memoryDeviceSize',
3093              '1.3.6.1.4.1.674.10892.1.1100.50.1.20.1' => 'memoryDeviceFailureModes',
3094             );
3095         my $result = undef;
3096         if ($opt{use_get_table}) {
3097             my $memoryDeviceTable = '1.3.6.1.4.1.674.10892.1.1100.50.1';
3098             $result = $snmp_session->get_table(-baseoid => $memoryDeviceTable);
3099         }
3100         else {
3101             $result = $snmp_session->get_entries(-columns => [keys %dimm_oid]);
3102         }
3103
3104         if (!defined $result) {
3105             printf "SNMP ERROR [memory]: %s.\n", $snmp_session->error;
3106             $snmp_session->close;
3107             exit $E_UNKNOWN;
3108         }
3109
3110         @output = @{ get_snmp_output($result, \%dimm_oid) };
3111     }
3112     else {
3113         @output = @{ run_omreport("$omopt_chassis memory") };
3114     }
3115
3116     # Note: These values are bit masks, so combination values are
3117     # possible. If value is 0 (zero), memory device has no faults.
3118     my %failure_mode
3119       = (
3120          1  => 'ECC single bit correction warning rate exceeded',
3121          2  => 'ECC single bit correction failure rate exceeded',
3122          4  => 'ECC multibit fault encountered',
3123          8  => 'ECC single bit correction logging disabled',
3124          16 => 'device disabled because of spare activation',
3125         );
3126
3127   DIMM:
3128     foreach my $out (@output) {
3129         @failures = ();  # Initialize
3130         if ($snmp) {
3131             $index    = ($out->{memoryDeviceIndex} || 10000) - 1;
3132             $status   = get_snmp_status($out->{memoryDeviceStatus});
3133             $location = $out->{memoryDeviceLocationName} || 'Unknown location';
3134             $size     = sprintf '%d MB', ($out->{memoryDeviceSize} || 0)/1024;
3135             $modes    = $out->{memoryDeviceFailureModes} || -9999;
3136             if ($modes > 0) {
3137                 foreach my $mask (sort keys %failure_mode) {
3138                     if (($modes & $mask) != 0) { push @failures, $failure_mode{$mask}; }
3139                 }
3140             }
3141             elsif ($modes == -9999) {
3142                 push @failures, q{ERROR: Failure modes not available via SNMP};
3143             }
3144         }
3145         else {
3146             my $type  = get_nonempty_string('Type', $out, q{});
3147             $index    = $type eq '[Not Occupied]' ? undef : get_nonempty_string('Index', $out, 9999);
3148             $status   = get_nonempty_string('Status', $out, 'Unknown');
3149             $location = get_nonempty_string('Connector Name', $out, 'Unknown location');
3150             $size     = get_nonempty_string('Size', $out, 0);
3151             if (defined $size) {
3152                 $size =~ s{\s\s}{ }gxms;
3153             }
3154             # Run 'omreport chassis memory index=X' to get the failures
3155             if ($status ne 'Ok' && defined $index) {
3156                 foreach (@{ run_command("$omreport $omopt_chassis memory index=$index -fmt ssv") }) {
3157                     if (m/\A Failures; (.+?) \z/xms) {
3158                         chop(my $fail = $1);
3159                         push @failures, split m{\.}xms, $fail;
3160                     }
3161                 }
3162             }
3163         }
3164         $location =~ s{\A \s*(.*?)\s* \z}{$1}xms;
3165
3166         # calculate total memory
3167         my $msize = defined $size ? $size : 0;
3168         $msize =~ s{\A (\d+) \s MB}{$1}xms;
3169         $count{mem} += $msize;
3170
3171         # Ignore empty memory slots
3172         next DIMM if !defined $index;
3173
3174         $count{dimm}++;
3175         next DIMM if blacklisted('dimm', $index);
3176
3177         if ($status ne 'Ok') {
3178             my $msg = undef;
3179             if (scalar @failures == 0) {
3180                 $msg = sprintf 'Memory module %d [%s, %s] needs attention (%s)',
3181                   $index, $location, $size, $status;
3182             }
3183             else {
3184                 $msg = sprintf 'Memory module %d [%s, %s] needs attention: %s',
3185                   $index, $location, $size, (join q{, }, @failures);
3186             }
3187
3188             report('chassis', $msg, $status2nagios{$status}, $index);
3189         }
3190         # Ok
3191         else {
3192             my $msg = sprintf 'Memory module %d [%s, %s] is %s',
3193               $index, $location, $size, $status;
3194             report('chassis', $msg, $E_OK, $index);
3195         }
3196     }
3197     return;
3198 }
3199
3200
3201 #-----------------------------------------
3202 # CHASSIS: Check fans
3203 #-----------------------------------------
3204 sub check_fans {
3205     my $index    = undef;
3206     my $status   = undef;
3207     my $reading  = undef;
3208     my $location = undef;
3209     my $max_crit = undef;
3210     my $max_warn = undef;
3211     my @output   = ();
3212
3213     if ($snmp) {
3214         my %cool_oid
3215           = (
3216              '1.3.6.1.4.1.674.10892.1.700.12.1.2.1'  => 'coolingDeviceIndex',
3217              '1.3.6.1.4.1.674.10892.1.700.12.1.5.1'  => 'coolingDeviceStatus',
3218              '1.3.6.1.4.1.674.10892.1.700.12.1.6.1'  => 'coolingDeviceReading',
3219              '1.3.6.1.4.1.674.10892.1.700.12.1.8.1'  => 'coolingDeviceLocationName',
3220              '1.3.6.1.4.1.674.10892.1.700.12.1.10.1' => 'coolingDeviceUpperCriticalThreshold',
3221              '1.3.6.1.4.1.674.10892.1.700.12.1.11.1' => 'coolingDeviceUpperNonCriticalThreshold',
3222             );
3223         my $result = undef;
3224         if ($opt{use_get_table}) {
3225             my $coolingDeviceTable = '1.3.6.1.4.1.674.10892.1.700.12.1';
3226             $result = $snmp_session->get_table(-baseoid => $coolingDeviceTable);
3227         }
3228         else {
3229             $result = $snmp_session->get_entries(-columns => [keys %cool_oid]);
3230         }
3231
3232         if ($blade && !defined $result) {
3233             return 0;
3234         }
3235         elsif (!$blade && !defined $result) {
3236             printf "SNMP ERROR [cooling]: %s.\n", $snmp_session->error;
3237             $snmp_session->close;
3238             exit $E_UNKNOWN;
3239         }
3240
3241         @output = @{ get_snmp_output($result, \%cool_oid) };
3242     }
3243     else {
3244         @output = @{ run_omreport("$omopt_chassis fans") };
3245     }
3246
3247   FAN:
3248     foreach my $out (@output) {
3249         if ($snmp) {
3250             $index    = ($out->{coolingDeviceIndex} || 10000) - 1;
3251             $status   = get_snmp_probestatus($out->{coolingDeviceStatus});
3252             $reading  = $out->{coolingDeviceReading} || 0;
3253             $location = $out->{coolingDeviceLocationName} || 'Unknown location';
3254             $max_crit = $out->{coolingDeviceUpperCriticalThreshold} || 0;
3255             $max_warn = $out->{coolingDeviceUpperNonCriticalThreshold} || 0;
3256         }
3257         else {
3258             $index    = get_nonempty_string('Index', $out, 9999);
3259             $status   = get_nonempty_string('Status', $out, 'Unknown');
3260             $reading  = get_nonempty_string('Reading', $out, 0);
3261             $location = get_nonempty_string('Probe Name', $out, 'Unknown location');
3262             $max_crit = get_nonempty_string('Maximum Failure Threshold', $out, 0);
3263             $max_warn = get_nonempty_string('Maximum Warning Threshold', $out, 0);
3264             if ($max_crit eq '[N/A]') { $max_crit = 0; }
3265             if ($max_warn eq '[N/A]') { $max_warn = 0; }
3266             $reading  =~ s{\A (\d+).* \z}{$1}xms;
3267             $max_warn =~ s{\A (\d+).* \z}{$1}xms;
3268             $max_crit =~ s{\A (\d+).* \z}{$1}xms;
3269         }
3270
3271         $count{fan}++;
3272         next FAN if blacklisted('fan', $index);
3273
3274         # Default
3275         my $msg = sprintf 'Chassis fan %d [%s] reading: %s RPM',
3276           $index, $location, $reading;
3277         my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3278         report('chassis', $msg, $err, $index);
3279
3280         # Collect performance data
3281         if (defined $opt{perfdata}) {
3282             my $pname = $location;
3283             $pname =~ s{\s}{_}gxms;
3284             $pname =~ s{proc_}{cpu#}xms;
3285             my $legacy_pname = $pname;
3286             $pname =~ s{_rpm\z}{}ixms;
3287             push @perfdata, {
3288                              type   => 'F',
3289                              id     => $index,
3290                              unit   => 'rpm',
3291                              label  => $pname,
3292                              legacy => lc "fan_${index}_${legacy_pname}",
3293                              mini   => "f$index",
3294                              value  => $reading,
3295                              warn   => $max_warn,
3296                              crit   => $max_crit,
3297                             };
3298         }
3299     }
3300     return;
3301 }
3302
3303
3304 #-----------------------------------------
3305 # CHASSIS: Check power supplies
3306 #-----------------------------------------
3307 sub check_powersupplies {
3308     my $index    = undef;
3309     my $status   = undef;
3310     my $type     = undef;
3311     my $err_type = undef;
3312     my $state    = undef;
3313     my @states   = ();
3314     my @output   = ();
3315
3316     if ($snmp) {
3317         my %ps_oid
3318           = (
3319              '1.3.6.1.4.1.674.10892.1.600.12.1.2.1'  => 'powerSupplyIndex',
3320              '1.3.6.1.4.1.674.10892.1.600.12.1.5.1'  => 'powerSupplyStatus',
3321              '1.3.6.1.4.1.674.10892.1.600.12.1.7.1'  => 'powerSupplyType',
3322              '1.3.6.1.4.1.674.10892.1.600.12.1.11.1' => 'powerSupplySensorState',
3323              '1.3.6.1.4.1.674.10892.1.600.12.1.12.1' => 'powerSupplyConfigurationErrorType',
3324             );
3325         my $result = undef;
3326         if ($opt{use_get_table}) {
3327             my $powerDeviceTable = '1.3.6.1.4.1.674.10892.1.600.12.1';
3328             $result = $snmp_session->get_table(-baseoid => $powerDeviceTable);
3329         }
3330         else {
3331             $result = $snmp_session->get_entries(-columns => [keys %ps_oid]);
3332         }
3333
3334         # No instrumented PSU is OK (blades, low-end servers)
3335         return 0 if !defined $result;
3336
3337         @output = @{ get_snmp_output($result, \%ps_oid) };
3338     }
3339     else {
3340         @output = @{ run_omreport("$omopt_chassis pwrsupplies") };
3341     }
3342
3343     my %ps_type
3344       = (
3345          1  => 'Other',
3346          2  => 'Unknown',
3347          3  => 'Linear',
3348          4  => 'Switching',
3349          5  => 'Battery',
3350          6  => 'Uninterruptible Power Supply',
3351          7  => 'Converter',
3352          8  => 'Regulator',
3353          9  => 'AC',
3354          10 => 'DC',
3355          11 => 'VRM',
3356         );
3357
3358     my %ps_state
3359       = (
3360          1  => 'Presence detected',
3361          2  => 'Failure detected',
3362          4  => 'Predictive Failure',
3363          8  => 'AC lost',
3364          16 => 'AC lost or out-of-range',
3365          32 => 'AC out-of-range but present',
3366          64 => 'Configuration error',
3367         );
3368
3369     my %ps_config_error_type
3370       = (
3371          1 => 'Vendor mismatch',
3372          2 => 'Revision mismatch',
3373          3 => 'Processor missing',
3374         );
3375
3376   PS:
3377     foreach my $out (@output) {
3378         if ($snmp) {
3379             @states = ();  # contains states for the PS
3380
3381             $index    = ($out->{powerSupplyIndex} || 10000) - 1;
3382             $status   = get_snmp_status($out->{powerSupplyStatus});
3383             $type     = get_hashval($out->{powerSupplyType}, \%ps_type) || 'Unknown type';
3384             $err_type = get_hashval($out->{powerSupplyConfigurationErrorType}, \%ps_config_error_type);
3385
3386             # get the combined state from the StatusReading OID
3387             my $raw_state = $out->{powerSupplySensorState} || 0;
3388             foreach my $mask (sort keys %ps_state) {
3389                 if (($raw_state & $mask) != 0) {
3390                     push @states, $ps_state{$mask};
3391                 }
3392             }
3393
3394             # If configuration error, also include the error type
3395             if (defined $err_type) {
3396                 push @states, $err_type;
3397             }
3398
3399             # Finally, construct the state string
3400             $state = join q{, }, @states;
3401         }
3402         else {
3403             $index  = get_nonempty_string('Index', $out, 9999);
3404             $status = get_nonempty_string('Status', $out, 'Unknown');
3405             $type   = get_nonempty_string('Type', $out, 'Unknown type');
3406             $state  = get_nonempty_string('Online Status', $out, 'Unknown state');
3407         }
3408
3409         $count{power}++;
3410         next PS if blacklisted('ps', $index);
3411
3412         if ($status ne 'Ok') {
3413             my $msg = sprintf 'Power Supply %d [%s] needs attention: %s',
3414               $index, $type, $state;
3415             report('chassis', $msg, $status2nagios{$status}, $index);
3416         }
3417         else {
3418             my $msg = sprintf 'Power Supply %d [%s]: %s',
3419               $index, $type, $state;
3420             report('chassis', $msg, $E_OK, $index);
3421         }
3422     }
3423     return;
3424 }
3425
3426
3427 #-----------------------------------------
3428 # CHASSIS: Check temperatures
3429 #-----------------------------------------
3430 sub check_temperatures {
3431     my $index    = undef;
3432     my $status   = undef;
3433     my $reading  = undef;
3434     my $location = undef;
3435     my $max_crit = undef;
3436     my $max_warn = undef;
3437     my $min_warn = undef;
3438     my $min_crit = undef;
3439     my $type     = undef;
3440     my $discrete = undef;
3441     my @output = ();
3442
3443     # Getting custom temperature thresholds (user option)
3444     my %warn_threshold = %{ custom_temperature_thresholds('w') };
3445     my %crit_threshold = %{ custom_temperature_thresholds('c') };
3446
3447     if ($snmp) {
3448         my %temp_oid
3449           = (
3450              '1.3.6.1.4.1.674.10892.1.700.20.1.2.1'  => 'temperatureProbeIndex',
3451              '1.3.6.1.4.1.674.10892.1.700.20.1.5.1'  => 'temperatureProbeStatus',
3452              '1.3.6.1.4.1.674.10892.1.700.20.1.6.1'  => 'temperatureProbeReading',
3453              '1.3.6.1.4.1.674.10892.1.700.20.1.7.1'  => 'temperatureProbeType',
3454              '1.3.6.1.4.1.674.10892.1.700.20.1.8.1'  => 'temperatureProbeLocationName',
3455              '1.3.6.1.4.1.674.10892.1.700.20.1.10.1' => 'temperatureProbeUpperCriticalThreshold',
3456              '1.3.6.1.4.1.674.10892.1.700.20.1.11.1' => 'temperatureProbeUpperNonCriticalThreshold',
3457              '1.3.6.1.4.1.674.10892.1.700.20.1.12.1' => 'temperatureProbeLowerNonCriticalThreshold',
3458              '1.3.6.1.4.1.674.10892.1.700.20.1.13.1' => 'temperatureProbeLowerCriticalThreshold',
3459              '1.3.6.1.4.1.674.10892.1.700.20.1.16.1' => 'temperatureProbeDiscreteReading',
3460             );
3461         # this didn't work well for some reason
3462         #my $result = $snmp_session->get_entries(-columns => [keys %temp_oid]);
3463
3464         # Getting values using the table
3465         my $temperatureProbeTable = '1.3.6.1.4.1.674.10892.1.700.20';
3466         my $result = $snmp_session->get_table(-baseoid => $temperatureProbeTable);
3467
3468         if (!defined $result) {
3469             printf "SNMP ERROR [temperatures]: %s.\n", $snmp_session->error;
3470             $snmp_session->close;
3471             exit $E_UNKNOWN;
3472         }
3473
3474         @output = @{ get_snmp_output($result, \%temp_oid) };
3475     }
3476     else {
3477         @output = @{ run_omreport("$omopt_chassis temps") };
3478     }
3479
3480     my %probe_type
3481       = (
3482          1  => 'Other',      # type is other than following values
3483          2  => 'Unknown',    # type is unknown
3484          3  => 'AmbientESM', # type is Ambient Embedded Systems Management temperature probe
3485          16 => 'Discrete',   # type is temperature probe with discrete reading
3486         );
3487
3488   TEMP:
3489     foreach my $out (@output) {
3490         if ($snmp) {
3491             $index    = ($out->{temperatureProbeIndex} || 10000) - 1;
3492             $status   = get_snmp_probestatus($out->{temperatureProbeStatus});
3493             $location = $out->{temperatureProbeLocationName} || 'Unknown location';
3494             $type     = get_hashval($out->{temperatureProbeType}, \%probe_type);
3495             $reading  = $out->{temperatureProbeReading} || '[N/A]';
3496             $max_crit = $out->{temperatureProbeUpperCriticalThreshold} || '[N/A]';
3497             $max_warn = $out->{temperatureProbeUpperNonCriticalThreshold} || '[N/A]';
3498             $min_crit = $out->{temperatureProbeLowerCriticalThreshold} || '[N/A]';
3499             $min_warn = $out->{temperatureProbeLowerNonCriticalThreshold} || '[N/A]';
3500             $discrete = $out->{temperatureProbeDiscreteReading} || '[N/A]';
3501
3502             # If numeric values, i.e. not discrete
3503             $reading  /= 10 if $reading  =~ m{\A \d+ \z}xms;
3504             $max_crit /= 10 if $max_crit =~ m{\A \d+ \z}xms;
3505             $max_warn /= 10 if $max_warn =~ m{\A \d+ \z}xms;
3506             $min_crit /= 10 if $min_crit =~ m{\A \d+ \z}xms;
3507             $min_warn /= 10 if $min_warn =~ m{\A \d+ \z}xms;
3508
3509             # workaround for bad temp probes
3510             if ($type eq 'AmbientESM' and $reading !~ m{\A \d+(\.\d+)? \z}xms) {
3511                 $type = 'Discrete';
3512             }
3513         }
3514         else {
3515             $index    = get_nonempty_string('Index', $out, 9999);
3516             $status   = get_nonempty_string('Status', $out, 'Unknown');
3517             $location = get_nonempty_string('Probe Name', $out, 'Unknown location');
3518             $reading  = get_nonempty_string('Reading', $out, '[N/A]');
3519             $max_crit = get_nonempty_string('Maximum Failure Threshold', $out, '[N/A]');
3520             $max_warn = get_nonempty_string('Maximum Warning Threshold', $out, '[N/A]');
3521             $min_crit = get_nonempty_string('Minimum Failure Threshold', $out, '[N/A]');
3522             $min_warn = get_nonempty_string('Minimum Warning Threshold', $out, '[N/A]');
3523
3524             # Cleaning the temp readings
3525             $reading =~ s{\.0\s+C}{}xms;
3526             $max_crit =~ s{\.0\s+C}{}xms;
3527             $max_warn =~ s{\.0\s+C}{}xms;
3528             $min_crit =~ s{\.0\s+C}{}xms;
3529             $min_warn =~ s{\.0\s+C}{}xms;
3530
3531             $type     = $reading =~ m{\A\d+\z}xms ? 'AmbientESM' : 'Discrete';
3532             $discrete = $reading;
3533         }
3534
3535         $count{temp}++;
3536         next TEMP if blacklisted('temp', $index);
3537
3538         # Convert temp units
3539         if ($opt{tempunit} ne 'C') {
3540             $reading  = temp_from_celsius($reading,  $opt{tempunit});
3541             $max_warn = temp_from_celsius($max_warn, $opt{tempunit});
3542             $max_crit = temp_from_celsius($max_crit, $opt{tempunit});
3543             $min_warn = temp_from_celsius($min_warn, $opt{tempunit});
3544             $min_crit = temp_from_celsius($min_crit, $opt{tempunit});
3545         }
3546
3547         if ($type eq 'Discrete') {
3548             my $msg = sprintf 'Temperature probe %d [%s] is %s',
3549               $index, $location, $discrete;
3550             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3551             report('chassis', $msg, $err, $index);
3552         }
3553         else {
3554             # First check according to custom thresholds
3555             if (exists $crit_threshold{$index}{max} and $reading > $crit_threshold{$index}{max}) {
3556                 # Custom critical MAX
3557                 my $msg = sprintf 'Temperature Probe %d [%s] reads %s %s (custom max=%s)',
3558                   $index, $location, $reading, $opt{tempunit}, $crit_threshold{$index}{max};
3559                 report('chassis', $msg, $E_CRITICAL, $index);
3560             }
3561             elsif (exists $warn_threshold{$index}{max} and $reading > $warn_threshold{$index}{max}) {
3562                 # Custom warning MAX
3563                 my $msg = sprintf 'Temperature Probe %d [%s] reads %s %s (custom max=%s)',
3564                   $index, $location, $reading, $opt{tempunit}, $warn_threshold{$index}{max};
3565                 report('chassis', $msg, $E_WARNING, $index);
3566             }
3567             elsif (exists $crit_threshold{$index}{min} and $reading < $crit_threshold{$index}{min}) {
3568                 # Custom critical MIN
3569                 my $msg = sprintf 'Temperature Probe %d [%s] reads %s %s (custom min=%s)',
3570                   $index, $location, $reading, $opt{tempunit}, $crit_threshold{$index}{min};
3571                 report('chassis', $msg, $E_CRITICAL, $index);
3572             }
3573             elsif (exists $warn_threshold{$index}{min} and $reading < $warn_threshold{$index}{min}) {
3574                 # Custom warning MIN
3575                 my $msg = sprintf 'Temperature Probe %d [%s] reads %s %s (custom min=%s)',
3576                   $index, $location, $reading, $opt{tempunit}, $warn_threshold{$index}{min};
3577                 report('chassis', $msg, $E_WARNING, $index);
3578             }
3579             elsif ($status ne 'Ok' and $max_crit ne '[N/A]' and $reading > $max_crit) {
3580                 my $msg = sprintf 'Temperature Probe %d [%s] is critically high at %s %s',
3581                   $index, $location, $reading, $opt{tempunit};
3582                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3583                 report('chassis', $msg, $err, $index);
3584             }
3585             elsif ($status ne 'Ok' and $max_warn ne '[N/A]' and $reading > $max_warn) {
3586                 my $msg = sprintf 'Temperature Probe %d [%s] is too high at %s %s',
3587                   $index, $location, $reading, $opt{tempunit};
3588                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3589                 report('chassis', $msg, $err, $index);
3590             }
3591             elsif ($status ne 'Ok' and $min_crit ne '[N/A]' and $reading < $min_crit) {
3592                 my $msg = sprintf 'Temperature Probe %d [%s] is critically low at %s %s',
3593                   $index, $location, $reading, $opt{tempunit};
3594                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3595                 report('chassis', $msg, $err, $index);
3596             }
3597             elsif ($status ne 'Ok' and $min_warn ne '[N/A]' and $reading < $min_warn) {
3598                 my $msg = sprintf 'Temperature Probe %d [%s] is too low at %s %s',
3599                   $index, $location, $reading, $opt{tempunit};
3600                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3601                 report('chassis', $msg, $err, $index);
3602             }
3603             # Ok
3604             else {
3605                 my $msg = sprintf 'Temperature Probe %d [%s] reads %s %s',
3606                   $index, $location, $reading, $opt{tempunit};
3607                 if ($min_warn eq '[N/A]' and $min_crit eq '[N/A]') {
3608                     $msg .= sprintf ' (max=%s/%s)', $max_warn, $max_crit;
3609                 }
3610                 else {
3611                     $msg .= sprintf ' (min=%s/%s, max=%s/%s)',
3612                       $min_warn, $min_crit, $max_warn, $max_crit;
3613                 }
3614                 my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
3615                 report('chassis', $msg, $err, $index);
3616             }
3617
3618             # Collect performance data
3619             if (defined $opt{perfdata}) {
3620                 my $pname = $location;
3621                 $pname =~ s{\s}{_}gxms;
3622                 $pname =~ s{_temp\z}{}ixms;
3623                 $pname =~ s{proc_}{cpu#}xms;
3624                 push @perfdata, {
3625                                  type   => 'T',
3626                                  id     => $index,
3627                                  unit   => $opt{tempunit},
3628                                  label  => $pname,
3629                                  legacy => lc "temp_${index}_${pname}",
3630                                  mini   => "t$index",
3631                                  value  => $reading,
3632                                  warn   => $max_warn,
3633                                  crit   => $max_crit,
3634                                 };
3635             }
3636         }
3637     }
3638     return;
3639 }
3640
3641
3642 #-----------------------------------------
3643 # CHASSIS: Check processors
3644 #-----------------------------------------
3645 sub check_processors {
3646     my $index   = undef;
3647     my $status  = undef;
3648     my $state   = undef;
3649     my $brand   = undef;
3650     my $family  = undef;
3651     my $man     = undef;
3652     my $speed   = undef;
3653     my @output = ();
3654
3655     if ($snmp) {
3656
3657         # NOTE: For some reason, older models don't have the
3658         # "Processor Device Status" OIDs. We check both the newer
3659         # (preferred) OIDs and the old ones.
3660
3661         my %cpu_oid
3662           = (
3663              '1.3.6.1.4.1.674.10892.1.1100.30.1.2.1'  => 'processorDeviceIndex',
3664              '1.3.6.1.4.1.674.10892.1.1100.30.1.5.1'  => 'processorDeviceStatus',
3665              '1.3.6.1.4.1.674.10892.1.1100.30.1.8.1'  => 'processorDeviceManufacturerName',
3666              '1.3.6.1.4.1.674.10892.1.1100.30.1.9.1'  => 'processorDeviceStatusState',
3667              '1.3.6.1.4.1.674.10892.1.1100.30.1.10.1' => 'processorDeviceFamily',
3668              '1.3.6.1.4.1.674.10892.1.1100.30.1.12.1' => 'processorDeviceCurrentSpeed',
3669              '1.3.6.1.4.1.674.10892.1.1100.30.1.23.1' => 'processorDeviceBrandName',
3670              '1.3.6.1.4.1.674.10892.1.1100.32.1.2.1'  => 'processorDeviceStatusIndex',
3671              '1.3.6.1.4.1.674.10892.1.1100.32.1.5.1'  => 'processorDeviceStatusStatus',
3672              '1.3.6.1.4.1.674.10892.1.1100.32.1.6.1'  => 'processorDeviceStatusReading',
3673             );
3674         my $result = undef;
3675         if ($opt{use_get_table}) {
3676             my $processorDeviceTable = '1.3.6.1.4.1.674.10892.1.1100.30.1';
3677             my $processorDeviceStatusTable = '1.3.6.1.4.1.674.10892.1.1100.32.1';
3678
3679             $result = $snmp_session->get_table(-baseoid => $processorDeviceTable);
3680             my $ext = $snmp_session->get_table(-baseoid => $processorDeviceStatusTable);
3681
3682             defined $ext && map { $$result{$_} = $$ext{$_} } keys %{ $ext };
3683         }
3684         else {
3685             $result = $snmp_session->get_entries(-columns => [keys %cpu_oid]);
3686         }
3687
3688         if (!defined $result) {
3689             printf "SNMP ERROR [processors]: %s.\n", $snmp_session->error;
3690             $snmp_session->close;
3691             exit $E_UNKNOWN;
3692         }
3693
3694         @output = @{ get_snmp_output($result, \%cpu_oid) };
3695     }
3696     else {
3697         @output = @{ run_omreport("$omopt_chassis processors") };
3698     }
3699
3700     my %cpu_state
3701       = (
3702          1 => 'Other',         # other than following values
3703          2 => 'Unknown',       # unknown
3704          3 => 'Enabled',       # enabled
3705          4 => 'User Disabled', # disabled by user via BIOS setup
3706          5 => 'BIOS Disabled', # disabled by BIOS (POST error)
3707          6 => 'Idle',          # idle
3708         );
3709
3710     my %cpu_reading
3711       = (
3712          1    => 'Internal Error',      # Internal Error
3713          2    => 'Thermal Trip',        # Thermal Trip
3714          32   => 'Configuration Error', # Configuration Error
3715          128  => 'Present',             # Processor Present
3716          256  => 'Disabled',            # Processor Disabled
3717          512  => 'Terminator Present',  # Terminator Present
3718          1024 => 'Throttled',           # Processor Throttled
3719         );
3720
3721     # Mapping between family numbers from SNMP and actual CPU family
3722     my %cpu_family
3723       = (
3724          1   => 'Other',                                2   => 'Unknown',
3725          3   => '8086',                                 4   => '80286',
3726          5   => '386',                                  6   => '486',
3727          7   => '8087',                                 8   => '80287',
3728          9   => '80387',                                10  => '80487',
3729          11  => 'Pentium',                              12  => 'Pentium Pro',
3730          13  => 'Pentium II',                           14  => 'Pentium with MMX',
3731          15  => 'Celeron',                              16  => 'Pentium II Xeon',
3732          17  => 'Pentium III',                          18  => 'Pentium III Xeon',
3733          19  => 'Pentium III',                          20  => 'Itanium',
3734          21  => 'Xeon',                                 22  => 'Pentium 4',
3735          23  => 'Xeon MP',                              24  => 'Itanium 2',
3736          25  => 'K5',                                   26  => 'K6',
3737          27  => 'K6-2',                                 28  => 'K6-3',
3738          29  => 'Athlon',                               30  => 'AMD2900',
3739          31  => 'K6-2+',                                32  => 'Power PC',
3740          33  => 'Power PC 601',                         34  => 'Power PC 603',
3741          35  => 'Power PC 603+',                        36  => 'Power PC 604',
3742          37  => 'Power PC 620',                         38  => 'Power PC x704',
3743          39  => 'Power PC 750',                         40  => 'Core Duo',
3744          41  => 'Core Duo mobile',                      42  => 'Core Solo mobile',
3745          43  => 'Intel Atom',                           44  => undef,
3746          45  => undef,                                  46  => undef,
3747          47  => undef,                                  48  => 'Alpha',
3748          49  => 'Alpha 21064',                          50  => 'Alpha 21066',
3749          51  => 'Alpha 21164',                          52  => 'Alpha 21164PC',
3750          53  => 'Alpha 21164a',                         54  => 'Alpha 21264',
3751          55  => 'Alpha 21364',                          56  => 'Turion II Ultra Dual-Core Mobile M',
3752          57  => 'Turion II Dual-Core Mobile M',         58  => 'Athlon II Dual-Core Mobile M ',
3753          59  => 'Opteron 6100',                         60  => 'Opteron 4100',
3754          61  => undef,                                  62  => undef,
3755          63  => undef,                                  64  => 'MIPS',
3756          65  => 'MIPS R4000',                           66  => 'MIPS R4200',
3757          67  => 'MIPS R4400',                           68  => 'MIPS R4600',
3758          69  => 'MIPS R10000',                          70  => undef,
3759          71  => undef,                                  72  => undef,
3760          73  => undef,                                  74  => undef,
3761          75  => undef,                                  76  => undef,
3762          77  => undef,                                  78  => undef,
3763          79  => undef,                                  80  => 'SPARC',
3764          81  => 'SuperSPARC',                           82  => 'microSPARC II',
3765          83  => 'microSPARC IIep',                      84  => 'UltraSPARC',
3766          85  => 'UltraSPARC II',                        86  => 'UltraSPARC IIi',
3767          87  => 'UltraSPARC III',                       88  => 'UltraSPARC IIIi',
3768          89  => undef,                                  90  => undef,
3769          91  => undef,                                  92  => undef,
3770          93  => undef,                                  94  => undef,
3771          95  => undef,                                  96  => '68040',
3772          97  => '68xxx',                                98  => '68000',
3773          99  => '68010',                                100 => '68020',
3774          101 => '68030',                                102 => undef,
3775          103 => undef,                                  104 => undef,
3776          105 => undef,                                  106 => undef,
3777          107 => undef,                                  108 => undef,
3778          109 => undef,                                  110 => undef,
3779          111 => undef,                                  112 => 'Hobbit',
3780          113 => undef,                                  114 => undef,
3781          115 => undef,                                  116 => undef,
3782          117 => undef,                                  118 => undef,
3783          119 => undef,                                  120 => 'Crusoe TM5000',
3784          121 => 'Crusoe TM3000',                        122 => 'Efficeon TM8000',
3785          123 => undef,                                  124 => undef,
3786          125 => undef,                                  126 => undef,
3787          127 => undef,                                  128 => 'Weitek',
3788          129 => undef,                                  130 => 'Celeron M',
3789          131 => 'Athlon 64',                            132 => 'Opteron',
3790          133 => 'Sempron',                              134 => 'Turion 64 Mobile',
3791          135 => 'Dual-Core Opteron',                    136 => 'Athlon 64 X2 DC',
3792          137 => 'Turion 64 X2 M',                       138 => 'Quad-Core Opteron',
3793          139 => '3rd gen Opteron',                      140 => 'AMD Phenom FX Quad-Core',
3794          141 => 'AMD Phenom X4 Quad-Core',              142 => 'AMD Phenom X2 Dual-Core',
3795          143 => 'AMD Athlon X2 Dual-Core',              144 => 'PA-RISC',
3796          145 => 'PA-RISC 8500',                         146 => 'PA-RISC 8000',
3797          147 => 'PA-RISC 7300LC',                       148 => 'PA-RISC 7200',
3798          149 => 'PA-RISC 7100LC',                       150 => 'PA-RISC 7100',
3799          151 => undef,                                  152 => undef,
3800          153 => undef,                                  154 => undef,
3801          155 => undef,                                  156 => undef,
3802          157 => undef,                                  158 => undef,
3803          159 => undef,                                  160 => 'V30',
3804          161 => 'Quad-Core Xeon 3200',                  162 => 'Dual-Core Xeon 3000',
3805          163 => 'Quad-Core Xeon 5300',                  164 => 'Dual-Core Xeon 5100',
3806          165 => 'Dual-Core Xeon 5000',                  166 => 'Dual-Core Xeon LV',
3807          167 => 'Dual-Core Xeon ULV',                   168 => 'Dual-Core Xeon 7100',
3808          169 => 'Quad-Core Xeon 5400',                  170 => 'Quad-Core Xeon',
3809          171 => 'Dual-Core Xeon 5200',                  172 => 'Dual-Core Xeon 7200',
3810          173 => 'Quad-Core Xeon 7300',                  174 => 'Quad-Core Xeon 7400',
3811          175 => 'Multi-Core Xeon 7400',                 176 => 'M1',
3812          177 => 'M2',                                   178 => undef,
3813          179 => 'Pentium 4 HT',                         180 => 'AS400',
3814          181 => undef,                                  182 => 'Athlon XP',
3815          183 => 'Athlon MP',                            184 => 'Duron',
3816          185 => 'Pentium M',                            186 => 'Celeron D',
3817          187 => 'Pentium D',                            188 => 'Pentium Extreme',
3818          189 => 'Core Solo',                            190 => 'Core2',
3819          191 => 'Core2 Duo',                            192 => 'Core2 Solo',
3820          193 => 'Core2 Extreme',                        194 => 'Core2 Quad',
3821          195 => 'Core2 Extreme mobile',                 196 => 'Core2 Duo mobile',
3822          197 => 'Core2 Solo mobile',                    198 => 'Core i7',
3823          199 => 'Dual-Core Celeron',                    200 => 'IBM390',
3824          201 => 'G4',                                   202 => 'G5',
3825          203 => 'ESA/390 G6',                           204 => 'z/Architectur',
3826          205 => 'Core i5',                              206 => 'Core i3',
3827          207 => undef,                                  208 => undef,
3828          209 => undef,                                  210 => 'C7-M',
3829          211 => 'C7-D',                                 212 => 'C7',
3830          213 => 'Eden',                                 214 => 'Multi-Core Xeon',
3831          215 => 'Dual-Core Xeon 3xxx',                  216 => 'Quad-Core Xeon 3xxx',
3832          217 => 'VIA Nano',                             218 => 'Dual-Core Xeon 5xxx',
3833          219 => 'Quad-Core Xeon 5xxx',                  220 => undef,
3834          221 => 'Dual-Core Xeon 7xxx',                  222 => 'Quad-Core Xeon 7xxx',
3835          223 => 'Multi-Core Xeon 7xxx',                 224 => 'Multi-Core Xeon 3400',
3836          225 => undef,                                  226 => undef,
3837          227 => undef,                                  228 => undef,
3838          229 => undef,                                  230 => 'Embedded AMD Opteron Quad-Core',
3839          231 => 'AMD Phenom Triple-Core',               232 => 'AMD Turion Ultra Dual-Core Mobile',
3840          233 => 'AMD Turion Dual-Core Mobile',          234 => 'AMD Athlon Dual-Core',
3841          235 => 'AMD Sempron SI',                       236 => 'AMD Phenom II',
3842          237 => 'AMD Athlon II',                        238 => 'Six-Core AMD Opteron',
3843          239 => 'AMD Sempron M',                        240 => undef,
3844          241 => undef,                                  242 => undef,
3845          243 => undef,                                  244 => undef,
3846          245 => undef,                                  246 => undef,
3847          247 => undef,                                  248 => undef,
3848          249 => undef,                                  250 => 'i860',
3849          251 => 'i960',
3850         );
3851
3852   CPU:
3853     foreach my $out (@output) {
3854         if ($snmp) {
3855             $index  = exists $out->{processorDeviceStatusIndex}
3856               ? ($out->{processorDeviceStatusIndex} || 10000) - 1
3857                 : ($out->{processorDeviceIndex} || 10000) - 1;
3858             $status = exists $out->{processorDeviceStatusStatus}
3859               ? get_snmp_status($out->{processorDeviceStatusStatus})
3860                 : get_snmp_status($out->{processorDeviceStatus});
3861             if (defined $out->{processorDeviceStatusReading}) {
3862                 my @states  = ();  # contains states for the CPU
3863
3864                 # get the combined state from the StatusReading OID
3865                 foreach my $mask (sort keys %cpu_reading) {
3866                     if (($out->{processorDeviceStatusReading} & $mask) != 0) {
3867                         push @states, $cpu_reading{$mask};
3868                     }
3869                 }
3870
3871                 # Finally, create the state string
3872                 $state = join q{, }, @states;
3873             }
3874             else {
3875                 $state  = get_hashval($out->{processorDeviceStatusState}, \%cpu_state) || 'Unknown state';
3876             }
3877             $man    = $out->{processorDeviceManufacturerName} || undef;
3878             $family = (defined $out->{processorDeviceFamily}
3879                        and defined $cpu_family{$out->{processorDeviceFamily}})
3880               ? $cpu_family{$out->{processorDeviceFamily}} : undef;
3881             $speed  = $out->{processorDeviceCurrentSpeed} || undef;
3882             $brand  = $out->{processorDeviceBrandName} || undef;
3883         }
3884         else {
3885             $index  = get_nonempty_string('Index', $out, 9999);
3886             $status = get_nonempty_string('Status', $out, 'Unknown');
3887             $state  = get_nonempty_string('State', $out, 'Unknown state');
3888             $brand  = get_nonempty_string('Processor Brand', $out, undef);
3889             $family = get_nonempty_string('Processor Family',  $out, undef);
3890             $man    = get_nonempty_string('Processor Manufacturer', $out, undef);
3891             $speed  = get_nonempty_string('Current Speed', $out, undef);
3892         }
3893
3894         # Ignore unoccupied CPU slots (omreport)
3895         next CPU if (defined $out->{'Processor Manufacturer'}
3896                      and $out->{'Processor Manufacturer'} eq '[Not Occupied]')
3897           or (defined $out->{'Processor Brand'} and $out->{'Processor Brand'} eq '[Not Occupied]');
3898
3899         # Ignore unoccupied CPU slots (snmp)
3900         if ($snmp and defined $out->{processorDeviceStatusReading}
3901             and $out->{processorDeviceStatusReading} == 0) {
3902             next CPU;
3903         }
3904
3905         $count{cpu}++;
3906         next CPU if blacklisted('cpu', $index);
3907
3908         if (defined $brand) {
3909             $brand =~ s{\s\s+}{ }gxms;
3910             $brand =~ s{\((R|tm)\)}{}gxms;
3911             $brand =~ s{\s(CPU|Processor)}{}xms;
3912             $brand =~ s{\s\@}{}xms;
3913         }
3914         elsif (defined $family and defined $man and defined $speed) {
3915             $speed =~ s{\A (\d+) .*}{$1}xms;
3916             $brand = sprintf '%s %s %.2fGHz', $man, $family, $speed / 1000;
3917         }
3918         else {
3919             $brand = "unknown";
3920         }
3921
3922         # Default
3923         if ($status ne 'Ok') {
3924             my $msg = sprintf 'Processor %d [%s] needs attention: %s',
3925               $index, $brand, $state;
3926             report('chassis', $msg, $status2nagios{$status}, $index);
3927         }
3928         # Ok
3929         else {
3930             my $msg = sprintf 'Processor %d [%s] is %s',
3931               $index, $brand, $state;
3932             report('chassis', $msg, $E_OK, $index);
3933         }
3934     }
3935     return;
3936 }
3937
3938
3939 #-----------------------------------------
3940 # CHASSIS: Check voltage probes
3941 #-----------------------------------------
3942 sub check_volts {
3943     my $index    = undef;
3944     my $status   = undef;
3945     my $reading  = undef;
3946     my $location = undef;
3947     my $max_crit = undef;
3948     my $max_warn = undef;
3949     my @output = ();
3950
3951     if ($snmp) {
3952         my %volt_oid
3953           = (
3954              '1.3.6.1.4.1.674.10892.1.600.20.1.2.1'  => 'voltageProbeIndex',
3955              '1.3.6.1.4.1.674.10892.1.600.20.1.5.1'  => 'voltageProbeStatus',
3956              '1.3.6.1.4.1.674.10892.1.600.20.1.6.1'  => 'voltageProbeReading',
3957              '1.3.6.1.4.1.674.10892.1.600.20.1.8.1'  => 'voltageProbeLocationName',
3958              '1.3.6.1.4.1.674.10892.1.600.20.1.16.1' => 'voltageProbeDiscreteReading',
3959             );
3960
3961         my $voltageProbeTable = '1.3.6.1.4.1.674.10892.1.600.20.1';
3962         my $result = $snmp_session->get_table(-baseoid => $voltageProbeTable);
3963
3964         if (!defined $result) {
3965             printf "SNMP ERROR [voltage]: %s.\n", $snmp_session->error;
3966             $snmp_session->close;
3967             exit $E_UNKNOWN;
3968         }
3969
3970         @output = @{ get_snmp_output($result, \%volt_oid) };
3971     }
3972     else {
3973         @output = @{ run_omreport("$omopt_chassis volts") };
3974     }
3975
3976     my %volt_discrete_reading
3977       = (
3978          1 => 'Good',
3979          2 => 'Bad',
3980         );
3981
3982   VOLT:
3983     foreach my $out (@output) {
3984         if ($snmp) {
3985             $index    = ($out->{voltageProbeIndex} || 10000) - 1;
3986             $status   = get_snmp_probestatus($out->{voltageProbeStatus});
3987             $reading  = defined $out->{voltageProbeReading}
3988               ? sprintf('%.3f V', $out->{voltageProbeReading}/1000)
3989                 : (get_hashval($out->{voltageProbeDiscreteReading}, \%volt_discrete_reading) || 'Unknown reading');
3990             $location = $out->{voltageProbeLocationName} || 'Unknown location';
3991             $max_crit = $out->{voltageProbeUpperCriticalThreshold} || 0;
3992             $max_warn = $out->{voltageProbeUpperNonCriticalThreshold} || 0;
3993         }
3994         else {
3995             $index    = get_nonempty_string('Index', $out, 9999);
3996             $status   = get_nonempty_string('Status', $out, 'Unknown');
3997             $reading  = get_nonempty_string('Reading', $out, 'Unknown reading');
3998             $location = get_nonempty_string('Probe Name', $out, 'Unknown location');
3999             $max_crit = get_nonempty_string('Maximum Failure Threshold', $out, 0);
4000             $max_warn = get_nonempty_string('Maximum Warning Threshold', $out, 0);
4001
4002             $max_crit = 0 if $max_crit eq '[N/A]';
4003             $max_warn = 0 if $max_warn eq '[N/A]';
4004         }
4005
4006         $count{volt}++;
4007         next VOLT if blacklisted('volt', $index);
4008
4009         my $msg = sprintf 'Voltage sensor %d [%s] is %s',
4010           $index, $location, $reading;
4011         my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
4012         report('chassis', $msg, $err, $index);
4013
4014         # Collect performance data
4015         if (defined $opt{perfdata} and !$opt{legacy_perfdata}) {
4016             $reading =~ s{\s+V\z}{}xms;  # remove unit
4017             $reading =~ s{\.000\z}{}xms; # if integer
4018             next VOLT if $reading !~ m{\A \d+(\.\d+)? \z}xms; # discrete reading (not number)
4019             my $label = join q{_}, $location;
4020             $label =~ s{\s}{_}gxms;
4021             push @perfdata, {
4022                              type   => 'V',
4023                              id     => $index,
4024                              unit   => 'V',
4025                              label  => $label,
4026                              value  => $reading,
4027                              warn   => 0,
4028                              crit   => 0,
4029                             };
4030         }
4031     }
4032     return;
4033 }
4034
4035
4036 #-----------------------------------------
4037 # CHASSIS: Check batteries
4038 #-----------------------------------------
4039 sub check_batteries {
4040     my $index    = undef;
4041     my $status   = undef;
4042     my $reading  = undef;
4043     my $location = undef;
4044     my @output = ();
4045
4046     if ($snmp) {
4047         my %bat_oid
4048           = (
4049              '1.3.6.1.4.1.674.10892.1.600.50.1.2.1' => 'batteryIndex',
4050              '1.3.6.1.4.1.674.10892.1.600.50.1.5.1' => 'batteryStatus',
4051              '1.3.6.1.4.1.674.10892.1.600.50.1.6.1' => 'batteryReading',
4052              '1.3.6.1.4.1.674.10892.1.600.50.1.7.1' => 'batteryLocationName',
4053             );
4054         my $result = undef;
4055         if ($opt{use_get_table}) {
4056             my $batteryTable = '1.3.6.1.4.1.674.10892.1.600.50.1';
4057             $result = $snmp_session->get_table(-baseoid => $batteryTable);
4058         }
4059         else {
4060             $result = $snmp_session->get_entries(-columns => [keys %bat_oid]);
4061         }
4062
4063         # No batteries is OK
4064         return 0 if !defined $result;
4065
4066         @output = @{ get_snmp_output($result, \%bat_oid) };
4067     }
4068     else {
4069         @output = @{ run_omreport("$omopt_chassis batteries") };
4070     }
4071
4072     my %bat_reading
4073       = (
4074          1 => 'Predictive Failure',
4075          2 => 'Failed',
4076          4 => 'Presence Detected',
4077         );
4078
4079   BATTERY:
4080     foreach my $out (@output) {
4081         if ($snmp) {
4082             $index    = ($out->{batteryIndex} || 10000) - 1;
4083             $status   = get_snmp_status($out->{batteryStatus});
4084             $reading  = get_hashval($out->{batteryReading}, \%bat_reading) || 'Unknown reading';
4085             $location = $out->{batteryLocationName} || 'Unknown location';
4086         }
4087         else {
4088             $index    = get_nonempty_string('Index', $out, 9999);
4089             $status   = get_nonempty_string('Status', $out, 'Unknown');
4090             $reading  = get_nonempty_string('Reading', $out, 'Unknown reading');
4091             $location = get_nonempty_string('Probe Name', $out, 'Unknown location');
4092         }
4093
4094         $count{bat}++;
4095         next BATTERY if blacklisted('bp', $index);
4096
4097         my $msg = sprintf 'Battery probe %d [%s] is %s',
4098           $index, $location, $reading;
4099         report('chassis', $msg, $status2nagios{$status}, $index);
4100     }
4101     return;
4102 }
4103
4104
4105 #-----------------------------------------
4106 # CHASSIS: Check amperage probes (power monitoring)
4107 #-----------------------------------------
4108 sub check_pwrmonitoring {
4109     my $index    = undef;
4110     my $status   = undef;
4111     my $reading  = undef;
4112     my $location = undef;
4113     my $max_crit = undef;
4114     my $max_warn = undef;
4115     my $unit     = undef;
4116     my $type     = undef;
4117     my @output = ();
4118
4119     if ($snmp) {
4120         my %amp_oid
4121           = (
4122              '1.3.6.1.4.1.674.10892.1.600.30.1.2.1'  => 'amperageProbeIndex',
4123              '1.3.6.1.4.1.674.10892.1.600.30.1.5.1'  => 'amperageProbeStatus',
4124              '1.3.6.1.4.1.674.10892.1.600.30.1.6.1'  => 'amperageProbeReading',
4125              '1.3.6.1.4.1.674.10892.1.600.30.1.7.1'  => 'amperageProbeType',
4126              '1.3.6.1.4.1.674.10892.1.600.30.1.8.1'  => 'amperageProbeLocationName',
4127              '1.3.6.1.4.1.674.10892.1.600.30.1.10.1' => 'amperageProbeUpperCriticalThreshold',
4128              '1.3.6.1.4.1.674.10892.1.600.30.1.11.1' => 'amperageProbeUpperNonCriticalThreshold',
4129              '1.3.6.1.4.1.674.10892.1.600.30.1.16.1' => 'amperageProbeDiscreteReading',
4130             );
4131         my $result = undef;
4132         if ($opt{use_get_table}) {
4133             my $amperageProbeTable = '1.3.6.1.4.1.674.10892.1.600.30.1';
4134             $result = $snmp_session->get_table(-baseoid => $amperageProbeTable);
4135         }
4136         else {
4137             $result = $snmp_session->get_entries(-columns => [keys %amp_oid]);
4138         }
4139
4140         # No pwrmonitoring is OK
4141         return 0 if !defined $result;
4142
4143         @output = @{ get_snmp_output($result, \%amp_oid) };
4144     }
4145     else {
4146         @output = @{ run_omreport("$omopt_chassis pwrmonitoring") };
4147     }
4148
4149     my %amp_type   # Amperage probe types
4150       = (
4151          1  => 'amperageProbeTypeIsOther',            # other than following values
4152          2  => 'amperageProbeTypeIsUnknown',          # unknown
4153          3  => 'amperageProbeTypeIs1Point5Volt',      # 1.5 amperage probe
4154          4  => 'amperageProbeTypeIs3Point3volt',      # 3.3 amperage probe
4155          5  => 'amperageProbeTypeIs5Volt',            # 5 amperage probe
4156          6  => 'amperageProbeTypeIsMinus5Volt',       # -5 amperage probe
4157          7  => 'amperageProbeTypeIs12Volt',           # 12 amperage probe
4158          8  => 'amperageProbeTypeIsMinus12Volt',      # -12 amperage probe
4159          9  => 'amperageProbeTypeIsIO',               # I/O probe
4160          10 => 'amperageProbeTypeIsCore',             # Core probe
4161          11 => 'amperageProbeTypeIsFLEA',             # FLEA (standby) probe
4162          12 => 'amperageProbeTypeIsBattery',          # Battery probe
4163          13 => 'amperageProbeTypeIsTerminator',       # SCSI Termination probe
4164          14 => 'amperageProbeTypeIs2Point5Volt',      # 2.5 amperage probe
4165          15 => 'amperageProbeTypeIsGTL',              # GTL (ground termination logic) probe
4166          16 => 'amperageProbeTypeIsDiscrete',         # amperage probe with discrete reading
4167          23 => 'amperageProbeTypeIsPowerSupplyAmps',  # Power Supply probe with reading in Amps
4168          24 => 'amperageProbeTypeIsPowerSupplyWatts', # Power Supply probe with reading in Watts
4169          25 => 'amperageProbeTypeIsSystemAmps',       # System probe with reading in Amps
4170          26 => 'amperageProbeTypeIsSystemWatts',      # System probe with reading in Watts
4171         );
4172
4173     my %amp_discrete
4174       = (
4175          1 => 'Good',
4176          2 => 'Bad',
4177         );
4178
4179     my %amp_unit
4180       = (
4181          'amperageProbeTypeIsPowerSupplyAmps'  => 'hA',  # tenths of Amps
4182          'amperageProbeTypeIsSystemAmps'       => 'hA',  # tenths of Amps
4183          'amperageProbeTypeIsPowerSupplyWatts' => 'W',   # Watts
4184          'amperageProbeTypeIsSystemWatts'      => 'W',   # Watts
4185          'amperageProbeTypeIsDiscrete'         => q{},   # discrete reading, no unit
4186         );
4187
4188   AMP:
4189     foreach my $out (@output) {
4190         if ($snmp) {
4191             $index    = ($out->{amperageProbeIndex} || 10000) - 1;
4192             $status   = get_snmp_probestatus($out->{amperageProbeStatus});
4193             $type     = get_hashval($out->{amperageProbeType}, \%amp_type);
4194             $reading  = $type eq 'amperageProbeTypeIsDiscrete'
4195               ? get_hashval($out->{amperageProbeDiscreteReading}, \%amp_discrete)
4196                 : ($out->{amperageProbeReading} || 0);
4197             $location = $out->{amperageProbeLocationName} || 'Unknown location';
4198             $max_crit = $out->{amperageProbeUpperCriticalThreshold} || 0;
4199             $max_warn = $out->{amperageProbeUpperNonCriticalThreshold} || 0;
4200             $unit     = exists $amp_unit{$amp_type{$out->{amperageProbeType}}}
4201               ? $amp_unit{$amp_type{$out->{amperageProbeType}}} : 'mA';
4202
4203             # calculate proper values and set unit for ampere probes
4204             if ($unit eq 'hA' and $type ne 'amperageProbeTypeIsDiscrete') {
4205                 $reading  /= 10;
4206                 $max_crit /= 10;
4207                 $max_warn /= 10;
4208                 $unit      = 'A';
4209             }
4210         }
4211         else {
4212             $index    = get_nonempty_string('Index', $out, 9999);
4213             $status   = get_nonempty_string('Status', $out, 'Unknown');
4214             $reading  = get_nonempty_string('Reading', $out, 'Unknown reading');
4215             $location = get_nonempty_string('Probe Name', $out, 'Unknown location');
4216             $max_crit = get_nonempty_string('Failure Threshold', $out, 0);
4217             $max_warn = get_nonempty_string('Warning Threshold', $out, 0);
4218
4219             $max_crit = 0 if $max_crit eq '[N/A]';
4220             $max_warn = 0 if $max_warn eq '[N/A]';
4221
4222             $reading  =~ s{\A (\d+.*?)\s+([a-zA-Z]+) \s*\z}{$1}xms;
4223             $unit     = $2 || 'unknown';
4224             $max_warn =~ s{\A (\d+.*?)\s+[a-zA-Z]+ \s*\z}{$1}xms;
4225             $max_crit =~ s{\A (\d+.*?)\s+[a-zA-Z]+ \s*\z}{$1}xms;
4226         }
4227
4228         next AMP if $index !~ m{\A \d+ \z}xms;
4229
4230         # Special case: Probe is present but unknown. This happens via
4231         # SNMP on some systems where power monitoring capability is
4232         # disabled due to non-redundant and/or non-instrumented power
4233         # supplies.
4234         # E.g. R410 with newer BMC firmware and 1 power supply
4235         if ($snmp && $status eq 'Unknown' && $reading == 0) {
4236             next AMP;
4237         }
4238
4239         $count{amp}++;
4240         next AMP if blacklisted('amp', $index);
4241
4242         # Special case: Discrete reading
4243         if (defined $type and $type eq 'amperageProbeTypeIsDiscrete') {
4244             my $msg = sprintf 'Amperage probe %d [%s] is %s',
4245               $index, $location, $reading;
4246             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
4247             report('chassis', $msg, $err, $index);
4248         }
4249         # Default
4250         else {
4251             my $msg = sprintf 'Amperage probe %d [%s] reads %s %s',
4252               $index, $location, $reading, $unit;
4253             my $err = $snmp ? $probestatus2nagios{$status} : $status2nagios{$status};
4254             report('chassis', $msg, $err, $index);
4255         }
4256
4257         # Collect performance data
4258         if (defined $opt{perfdata}) {
4259             next AMP if $reading !~ m{\A \d+(\.\d+)? \z}xms; # discrete reading (not number)
4260             my $label = join q{_},  $location;
4261             $label =~ s{\s}{_}gxms;
4262             push @perfdata, {
4263                              type   => $unit,
4264                              id     => $index,
4265                              unit   => $unit,
4266                              label  => $label,
4267                              legacy => (join q{_}, 'pwr_mon', $index, lc $label),
4268                              mini   => "p${index}" . lc $unit,
4269                              value  => $reading,
4270                              warn   => $max_warn,
4271                              crit   => $max_crit,
4272                             };
4273         }
4274     }
4275
4276     # Collect EXTRA performance data not found at first run. This is a
4277     # rather ugly hack
4278     if (defined $opt{perfdata} && !$snmp) {
4279         my $found = 0;
4280         my $index = 0;
4281         my %used  = ();
4282
4283         # find used indexes
4284         foreach (@perfdata) {
4285             if ($_->{label} =~ m/\A [WA](\d+)/xms) {
4286                 $used{$1} = 1;
4287             }
4288         }
4289
4290       AMP2:
4291         foreach my $line (@{ run_command("$omreport $omopt_chassis pwrmonitoring -fmt ssv") }) {
4292             chop $line;
4293             if ($line eq 'Location;Reading' or $line eq 'Amperage') {
4294                 $found = 1;
4295                 next AMP2;
4296             }
4297             if ($line eq q{}) {
4298                 $found = 0;
4299                 next AMP2;
4300             }
4301             if ($found and $line =~ m/\A ([^;]+?) ; (\d*\.\d+) \s ([AW]) \z/xms) {
4302                 my $aname = $1;
4303                 my $aval  = $2;
4304                 my $aunit = $3;
4305                 $aname =~ s{\s}{_}gxms;
4306
4307                 # don't use an existing index
4308                 while (exists $used{$index}) { ++$index; }
4309
4310                 push @perfdata, {
4311                                  type   => $aunit,
4312                                  id     => $index,
4313                                  unit   => $aunit,
4314                                  label  => $aname,
4315                                  legacy => "pwr_mon_${index}_${aname}",
4316                                  mini   => "p${index}a",
4317                                  value  => $aval,
4318                                  warn   => 0,
4319                                  crit   => 0,
4320                                 };
4321                 ++$index;
4322             }
4323         }
4324     }
4325
4326     return;
4327 }
4328
4329
4330 #-----------------------------------------
4331 # CHASSIS: Check intrusion
4332 #-----------------------------------------
4333 sub check_intrusion {
4334     my $index    = undef;
4335     my $status   = undef;
4336     my $reading  = undef;
4337     my @output = ();
4338
4339     if ($snmp) {
4340         my %int_oid
4341           = (
4342              '1.3.6.1.4.1.674.10892.1.300.70.1.2.1' => 'intrusionIndex',
4343              '1.3.6.1.4.1.674.10892.1.300.70.1.5.1' => 'intrusionStatus',
4344              '1.3.6.1.4.1.674.10892.1.300.70.1.6.1' => 'intrusionReading',
4345             );
4346         my $result = undef;
4347         if ($opt{use_get_table}) {
4348             my $intrusionTable = '1.3.6.1.4.1.674.10892.1.300.70.1';
4349             $result = $snmp_session->get_table(-baseoid => $intrusionTable);
4350         }
4351         else {
4352             $result = $snmp_session->get_entries(-columns => [keys %int_oid]);
4353         }
4354
4355         # No intrusion is OK
4356         return 0 if !defined $result;
4357
4358         @output = @{ get_snmp_output($result, \%int_oid) };
4359     }
4360     else {
4361         @output = @{ run_omreport("$omopt_chassis intrusion") };
4362     }
4363
4364     my %int_reading
4365       = (
4366          1 => 'Not Breached',          # chassis not breached and no uncleared breaches
4367          2 => 'Breached',              # chassis currently breached
4368          3 => 'Breached Prior',        # chassis breached prior to boot and has not been cleared
4369          4 => 'Breach Sensor Failure', # intrusion sensor has failed
4370         );
4371
4372   INTRUSION:
4373     foreach my $out (@output) {
4374         if ($snmp) {
4375             $index    = ($out->{intrusionIndex} || 10000) - 1;
4376             $status   = get_snmp_status($out->{intrusionStatus});
4377             $reading  = get_hashval($out->{intrusionReading}, \%int_reading) || 'Unknown reading';
4378         }
4379         else {
4380             $index    = get_nonempty_string('Index', $out, 9999);
4381             $status   = get_nonempty_string('Status', $out, 'Unknown');
4382             $reading  = get_nonempty_string('State', $out, 'Unknown reading');
4383         }
4384
4385         $count{intr}++;
4386         next INTRUSION if blacklisted('intr', $index);
4387
4388         if ($status ne 'Ok') {
4389             my $msg = sprintf 'Chassis intrusion %d detected: %s',
4390               $index, $reading;
4391             report('chassis', $msg, $E_WARNING, $index);
4392         }
4393         # Ok
4394         else {
4395             my $msg = sprintf 'Chassis intrusion %d detection: %s (%s)',
4396               $index, $status, $reading;
4397             report('chassis', $msg, $E_OK, $index);
4398         }
4399     }
4400     return;
4401 }
4402
4403
4404 #-----------------------------------------
4405 # CHASSIS: Check SD Card Device
4406 #-----------------------------------------
4407 sub check_sdcard {
4408     my $index    = undef;
4409     my $status   = undef;
4410     my $state    = undef;
4411     my $location = undef;
4412     my $capacity = undef;
4413     my $setting  = undef;
4414     my @output = ();
4415
4416     if ($snmp) {
4417         my %sd_oid
4418           = (
4419              '1.3.6.1.4.1.674.10892.1.1100.112.1.2.1'  => 'sdCardDeviceIndex',
4420              '1.3.6.1.4.1.674.10892.1.1100.112.1.3.1'  => 'sdCardDeviceStatus',
4421              '1.3.6.1.4.1.674.10892.1.1100.112.1.4.1'  => 'sdCardDeviceType',
4422              '1.3.6.1.4.1.674.10892.1.1100.112.1.7.1'  => 'sdCardDeviceLocationName',
4423              '1.3.6.1.4.1.674.10892.1.1100.112.1.8.1'  => 'sdCardDeviceCardPresent',
4424              '1.3.6.1.4.1.674.10892.1.1100.112.1.9.1'  => 'sdCardDeviceCardState',
4425              '1.3.6.1.4.1.674.10892.1.1100.112.1.10.1' => 'sdCardDeviceCardStorageSize',
4426             );
4427         my $result = undef;
4428         if ($opt{use_get_table}) {
4429             my $sdCardDeviceTable = '1.3.6.1.4.1.674.10892.1.1100.112.1';
4430             $result = $snmp_session->get_table(-baseoid => $sdCardDeviceTable);
4431         }
4432         else {
4433             $result = $snmp_session->get_entries(-columns => [keys %sd_oid]);
4434         }
4435
4436         # No SD cards is OK
4437         return 0 if !defined $result;
4438
4439         @output = @{ get_snmp_output($result, \%sd_oid) };
4440     }
4441     else {
4442         @output = @{ run_omreport("$omopt_chassis removableflashmedia") };
4443     }
4444
4445     # Note: These values are bit fields, so combination values are possible.
4446     my %sd_state
4447       = (
4448          0   => 'None',            # state is none of the following:
4449          1   => 'Present',         # device is present
4450          2   => 'IPMI-ready',      # device is IPMI ready
4451          4   => 'Full-ready',      # device is full ready
4452          8   => 'Offline',         # device is offline
4453          16  => 'Failed',          # device is failed
4454          32  => 'Active',          # device is active
4455          64  => 'Bootable',        # device is bootable
4456          128 => 'Write-protected', # device is write-protected
4457          256 => 'Standby',         # device is in standby mode
4458         );
4459
4460     my $c = 0;
4461   SDCARD:
4462     foreach my $out (@output) {
4463         if ($snmp) {
4464             $index    = ($out->{sdCardDeviceIndex} || 10000) - 1;
4465             $status   = get_snmp_status($out->{sdCardDeviceStatus});
4466
4467             if (defined $out->{sdCardDeviceCardState}) {
4468                 my @states  = ();  # contains states SD card
4469
4470                 # get the combined state from the Device Status OID
4471                 foreach my $mask (sort keys %sd_state) {
4472                     if (($out->{sdCardDeviceCardState} & $mask) != 0) {
4473                         push @states, $sd_state{$mask};
4474                     }
4475                 }
4476
4477                 # Finally, create the state string
4478                 $state = join q{, }, @states;
4479
4480                 # special case: absent
4481                 if ($out->{sdCardDeviceCardState} % 2 == 0) {
4482                     $state = 'Absent';
4483                 }
4484             }
4485
4486             $location = $out->{sdCardDeviceLocationName} || 'Unknown location';
4487             $capacity = sprintf '%s MB', ($out->{sdCardDeviceCardStorageSize} || 'Unknown size');
4488         }
4489         else {
4490             $index    = $c++;
4491             $status   = get_nonempty_string('Status', $out, 'Ok');
4492             $state    = get_nonempty_string('State', $out, 'Unknown state');
4493             $location = get_nonempty_string('Connector Name', $out, 'Unknown location');
4494             $capacity = get_nonempty_string('Storage Size', $out, 'Unknown size');
4495
4496             $capacity =~ s{\[Not Available\]}{Unknown Size};
4497         }
4498
4499         $count{sd}++ if $state ne 'Absent';
4500         next SDCARD if blacklisted('sd', $index);
4501
4502         if ($status ne 'Ok') {
4503             my $msg = sprintf 'SD Card %d needs attention: %s',
4504               $index, $state;
4505             report('chassis', $msg, $E_WARNING, $index);
4506         }
4507         # Special case: Not Present
4508         elsif ($status eq 'Ok' and $state eq 'Absent') {
4509             my $msg = sprintf 'SD Card %d [%s] is %s',
4510               $index, $location, $state;
4511             report('chassis', $msg, $E_OK, $index);
4512         }
4513         # Ok
4514         else {
4515             my $msg = sprintf 'SD Card %d [%s, %s] is %s',
4516               $index, $location, $capacity, $state;
4517             report('chassis', $msg, $E_OK, $index);
4518         }
4519     }
4520     return;
4521 }
4522
4523
4524 #-----------------------------------------
4525 # CHASSIS: Check alert log
4526 #-----------------------------------------
4527 sub check_alertlog {
4528     return if $snmp; # Not supported with SNMP
4529
4530     my @output = @{ run_omreport("$omopt_system alertlog") };
4531     foreach my $out (@output) {
4532         ++$count{alert}{$out->{Severity}};
4533     }
4534
4535     # Create error messages and set exit value if appropriate
4536     my $err = 0;
4537     if ($count{alert}{'Critical'} > 0)        { $err = $E_CRITICAL; }
4538     elsif ($count{alert}{'Non-Critical'} > 0) { $err = $E_WARNING;  }
4539
4540     my $msg = sprintf 'Alert log content: %d critical, %d non-critical, %d ok',
4541       $count{alert}{'Critical'}, $count{alert}{'Non-Critical'}, $count{alert}{'Ok'};
4542     report('other', $msg, $err);
4543
4544     return;
4545 }
4546
4547 #-----------------------------------------
4548 # CHASSIS: Check ESM log overall health
4549 #-----------------------------------------
4550 sub check_esmlog_health {
4551     my $health = 'Ok';
4552
4553     if ($snmp) {
4554         my $systemStateEventLogStatus = '1.3.6.1.4.1.674.10892.1.200.10.1.41.1';
4555         my $result = $snmp_session->get_request(-varbindlist => [$systemStateEventLogStatus]);
4556         if (!defined $result) {
4557             my $msg = sprintf 'SNMP ERROR [esmhealth]: %s',
4558               $snmp_session->error;
4559             report('other', $msg, $E_UNKNOWN);
4560         }
4561         $health = get_snmp_status($result->{$systemStateEventLogStatus});
4562     }
4563     else {
4564         foreach (@{ run_command("$omreport $omopt_system esmlog -fmt ssv") }) {
4565             if (m/\A Health;(.+) \z/xms) {
4566                 $health = $1;
4567                 chop $health;
4568                 last;
4569             }
4570         }
4571     }
4572
4573     # If the overall health of the ESM log is other than "Ok", the
4574     # fill grade of the log is more than 80% and the log should be
4575     # cleared
4576     if ($health eq 'Ok') {
4577         my $msg = sprintf 'ESM log health is Ok (less than 80%% full)';
4578         report('other', $msg, $E_OK);
4579     }
4580     elsif ($health eq 'Critical') {
4581         my $msg = sprintf 'ESM log is 100%% full';
4582         report('other', $msg, $status2nagios{$health});
4583     }
4584     else {
4585         my $msg = sprintf 'ESM log is more than 80%% full';
4586         report('other', $msg, $status2nagios{$health});
4587     }
4588
4589     return;
4590 }
4591
4592 #-----------------------------------------
4593 # CHASSIS: Check ESM log
4594 #-----------------------------------------
4595 sub check_esmlog {
4596     my @output = ();
4597
4598     if ($snmp) {
4599         my %esm_oid
4600           = (
4601              '1.3.6.1.4.1.674.10892.1.300.40.1.7.1'  => 'eventLogSeverityStatus',
4602             );
4603         my $result = $snmp_session->get_entries(-columns => [keys %esm_oid]);
4604
4605         # No entries is OK
4606         return if !defined $result;
4607
4608         @output = @{ get_snmp_output($result, \%esm_oid) };
4609         foreach my $out (@output) {
4610             ++$count{esm}{$snmp_status{$out->{eventLogSeverityStatus}}};
4611         }
4612     }
4613     else {
4614         @output = @{ run_omreport("$omopt_system esmlog") };
4615         foreach my $out (@output) {
4616             ++$count{esm}{$out->{Severity}};
4617         }
4618     }
4619
4620     # Create error messages and set exit value if appropriate
4621     my $err = 0;
4622     if ($count{esm}{'Critical'} > 0)        { $err = $E_CRITICAL; }
4623     elsif ($count{esm}{'Non-Critical'} > 0) { $err = $E_WARNING;  }
4624
4625     my $msg = sprintf 'ESM log content: %d critical, %d non-critical, %d ok',
4626       $count{esm}{'Critical'}, $count{esm}{'Non-Critical'}, $count{esm}{'Ok'};
4627     report('other', $msg, $err);
4628
4629     return;
4630 }
4631
4632 #
4633 # Handy function for checking all storage components
4634 #
4635 sub check_storage {
4636     check_controllers();
4637     check_physical_disks();
4638     check_virtual_disks();
4639     check_cache_battery();
4640     check_connectors();
4641     check_enclosures();
4642     check_enclosure_fans();
4643     check_enclosure_pwr();
4644     check_enclosure_temp();
4645     check_enclosure_emms();
4646     return;
4647 }
4648
4649
4650
4651 #---------------------------------------------------------------------
4652 # Info functions
4653 #---------------------------------------------------------------------
4654
4655 #
4656 # Fetch output from 'omreport chassis info', put in sysinfo hash
4657 #
4658 sub get_omreport_chassis_info {
4659     if (open my $INFO, '-|', "$omreport $omopt_chassis info -fmt ssv") {
4660         my @lines = <$INFO>;
4661         close $INFO;
4662         foreach (@lines) {
4663             next if !m/\A (Chassis\sModel|Chassis\sService\sTag|Model|Service\sTag|System\sRevision)/xms;
4664             my ($key, $val) = split /;/xms;
4665             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4666             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4667             if ($key eq 'Chassis Model' or $key eq 'Model') {
4668                 $sysinfo{model}  = $val;
4669             }
4670             if ($key eq 'Chassis Service Tag' or $key eq 'Service Tag') {
4671                 $sysinfo{serial} = $val;
4672             }
4673             if ($key eq 'System Revision') {
4674                 $sysinfo{rev} = q{ } . $val;
4675             }
4676         }
4677     }
4678     return;
4679 }
4680
4681 #
4682 # Fetch output from 'omreport chassis bios', put in sysinfo hash
4683 #
4684 sub get_omreport_chassis_bios {
4685     if (open my $BIOS, '-|', "$omreport $omopt_chassis bios -fmt ssv") {
4686         my @lines = <$BIOS>;
4687         close $BIOS;
4688         foreach (@lines) {
4689             next if !m/;/xms;
4690             my ($key, $val) = split /;/xms;
4691             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4692             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4693             $sysinfo{bios}     = $val if $key eq 'Version';
4694             $sysinfo{biosdate} = $val if $key eq 'Release Date';
4695         }
4696     }
4697     return;
4698 }
4699
4700 #
4701 # Fetch output from 'omreport system operatingsystem', put in sysinfo hash
4702 #
4703 sub get_omreport_system_operatingsystem {
4704     if (open my $VER, '-|', "$omreport $omopt_system operatingsystem -fmt ssv") {
4705         my @lines = <$VER>;
4706         close $VER;
4707         foreach (@lines) {
4708             next if !m/;/xms;
4709             my ($key, $val) = split /;/xms;
4710             $key =~ s{\s+\z}{}xms; # remove trailing whitespace
4711             $val =~ s{\s+\z}{}xms; # remove trailing whitespace
4712             if ($key eq 'Operating System') {
4713                 $sysinfo{osname} = $val;
4714             }
4715             elsif ($key eq 'Operating System Version') {
4716                 $sysinfo{osver}  = $val;
4717             }
4718         }
4719     }
4720     return;
4721 }
4722
4723 #
4724 # Fetch output from 'omreport about', put in sysinfo hash
4725 #
4726 sub get_omreport_about {
4727     if (open my $OM, '-|', "$omreport about -fmt ssv") {
4728         my @lines = <$OM>;
4729         close $OM;
4730         foreach (@lines) {
4731             if (m/\A Version;(.+) \z/xms) {
4732                 $sysinfo{om} = $1;
4733                 chomp $sysinfo{om};
4734             }
4735         }
4736     }
4737     return;
4738 }
4739
4740 #
4741 # Fetch chassis info via SNMP, put in sysinfo hash
4742 #
4743 sub get_snmp_chassis_info {
4744     my %chassis_oid
4745       = (
4746          '1.3.6.1.4.1.674.10892.1.300.10.1.9.1'  => 'chassisModelName',
4747          '1.3.6.1.4.1.674.10892.1.300.10.1.11.1' => 'chassisServiceTagName',
4748          '1.3.6.1.4.1.674.10892.1.300.10.1.48.1' => 'chassisSystemRevisionName',
4749         );
4750
4751     my $chassisInformationTable = '1.3.6.1.4.1.674.10892.1.300.10.1';
4752     my $result = $snmp_session->get_table(-baseoid => $chassisInformationTable);
4753
4754     if (defined $result) {
4755         foreach my $oid (keys %{ $result }) {
4756             if (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisModelName') {
4757                 $sysinfo{model} = $result->{$oid};
4758                 $sysinfo{model} =~ s{\s+\z}{}xms; # remove trailing whitespace
4759             }
4760             elsif (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisServiceTagName') {
4761                 $sysinfo{serial} = $result->{$oid};
4762             }
4763             elsif (exists $chassis_oid{$oid} and $chassis_oid{$oid} eq 'chassisSystemRevisionName') {
4764                 $sysinfo{rev} = q{ } . $result->{$oid};
4765             }
4766         }
4767     }
4768     else {
4769         my $msg = sprintf 'SNMP ERROR getting chassis info: %s',
4770           $snmp_session->error;
4771         report('other', $msg, $E_UNKNOWN);
4772     }
4773     return;
4774 }
4775
4776 #
4777 # Fetch BIOS info via SNMP, put in sysinfo hash
4778 #
4779 sub get_snmp_chassis_bios {
4780     my %bios_oid
4781       = (
4782          '1.3.6.1.4.1.674.10892.1.300.50.1.7.1.1' => 'systemBIOSReleaseDateName',
4783          '1.3.6.1.4.1.674.10892.1.300.50.1.8.1.1' => 'systemBIOSVersionName',
4784         );
4785
4786     my $systemBIOSTable = '1.3.6.1.4.1.674.10892.1.300.50.1';
4787     my $result = $snmp_session->get_table(-baseoid => $systemBIOSTable);
4788
4789     if (defined $result) {
4790         foreach my $oid (keys %{ $result }) {
4791             if (exists $bios_oid{$oid} and $bios_oid{$oid} eq 'systemBIOSReleaseDateName') {
4792                 $sysinfo{biosdate} = $result->{$oid};
4793                 $sysinfo{biosdate} =~ s{\A (\d{4})(\d{2})(\d{2}).*}{$2/$3/$1}xms;
4794             }
4795             elsif (exists $bios_oid{$oid} and $bios_oid{$oid} eq 'systemBIOSVersionName') {
4796                 $sysinfo{bios} = $result->{$oid};
4797             }
4798         }
4799     }
4800     else {
4801         my $msg = sprintf 'SNMP ERROR getting BIOS info: %s',
4802           $snmp_session->error;
4803         report('other', $msg, $E_UNKNOWN);
4804     }
4805     return;
4806 }
4807
4808 #
4809 # Fetch OS info via SNMP, put in sysinfo hash
4810 #
4811 sub get_snmp_system_operatingsystem {
4812     my %os_oid
4813       = (
4814          '1.3.6.1.4.1.674.10892.1.400.10.1.6.1' => 'operatingSystemOperatingSystemName',
4815          '1.3.6.1.4.1.674.10892.1.400.10.1.7.1' => 'operatingSystemOperatingSystemVersionName',
4816         );
4817
4818     my $operatingSystemTable = '1.3.6.1.4.1.674.10892.1.400.10.1';
4819     my $result = $snmp_session->get_table(-baseoid => $operatingSystemTable);
4820
4821     if (defined $result) {
4822         foreach my $oid (keys %{ $result }) {
4823             if (exists $os_oid{$oid} and $os_oid{$oid} eq 'operatingSystemOperatingSystemName') {
4824                 $sysinfo{osname} = ($result->{$oid});
4825             }
4826             elsif (exists $os_oid{$oid} and $os_oid{$oid} eq 'operatingSystemOperatingSystemVersionName') {
4827                 $sysinfo{osver} = $result->{$oid};
4828             }
4829         }
4830     }
4831     else {
4832         my $msg = sprintf 'SNMP ERROR getting OS info: %s',
4833           $snmp_session->error;
4834         report('other', $msg, $E_UNKNOWN);
4835     }
4836     return;
4837 }
4838
4839 #
4840 # Fetch OMSA version via SNMP, put in sysinfo hash
4841 #
4842 sub get_snmp_about {
4843     # systemManagementSoftwareGlobalVersionName
4844     my $oid = '1.3.6.1.4.1.674.10892.1.100.10.0';
4845     my $result = $snmp_session->get_request(-varbindlist => [$oid]);
4846
4847     if (defined $result) {
4848         $sysinfo{om} = exists $result->{$oid} && $result->{$oid} ne q{}
4849           ? $result->{$oid} : 'unknown';
4850     }
4851     else {
4852         my $msg = sprintf 'SNMP ERROR: Getting OMSA version failed: %s', $snmp_session->error;
4853         report('other', $msg, $E_UNKNOWN);
4854     }
4855     return;
4856 }
4857
4858 #
4859 # Collects some information about the system
4860 #
4861 sub get_sysinfo
4862 {
4863     # Get system model and serial number
4864     $snmp ? get_snmp_chassis_info() : get_omreport_chassis_info();
4865
4866     # Get BIOS information. Only if needed
4867     if ( $opt{okinfo} >= 1
4868          or $opt{debug}
4869          or (defined $opt{postmsg} and $opt{postmsg} =~ m/[%][bd]/xms) ) {
4870         $snmp ? get_snmp_chassis_bios() : get_omreport_chassis_bios();
4871     }
4872
4873     # Get OMSA information. Only if needed
4874     if ($opt{okinfo} >= 3 or $opt{debug}) {
4875         $snmp ? get_snmp_about() : get_omreport_about();
4876     }
4877
4878     # Return now if debug
4879     return if $opt{debug};
4880
4881     # Get OS information. Only if needed
4882     if (defined $opt{postmsg} and $opt{postmsg} =~ m/[%][or]/xms) {
4883         $snmp ? get_snmp_system_operatingsystem() : get_omreport_system_operatingsystem();
4884     }
4885
4886     return;
4887 }
4888
4889
4890 # Helper function for running omreport when the results are strictly
4891 # name=value pairs.
4892 sub run_omreport_info {
4893     my $command = shift;
4894     my %output  = ();
4895     my @keys    = ();
4896
4897     # Run omreport and fetch output
4898     my $rawtext = slurp_command("$omreport $command -fmt ssv 2>&1");
4899
4900     # Parse output, store in array
4901     for ((split /\n/xms, $rawtext)) {
4902         if (m/\A Error/xms) {
4903             my $msg = "Problem running 'omreport $command': $_";
4904             report('other', $msg, $E_UNKNOWN);
4905         }
4906         next if !m/;/xms;  # ignore lines with less than two fields
4907         my @vals = split m/;/xms;
4908         $output{$vals[0]} = $vals[1];
4909     }
4910
4911     # Finally, return the collected information
4912     return \%output;
4913 }
4914
4915 # Get various firmware information (BMC, RAC)
4916 sub get_firmware_info {
4917     my @snmp_output = ();
4918     my %nrpe_output = ();
4919
4920     if ($snmp) {
4921         my %fw_oid
4922           = (
4923              '1.3.6.1.4.1.674.10892.1.300.60.1.7.1'  => 'firmwareType',
4924              '1.3.6.1.4.1.674.10892.1.300.60.1.8.1'  => 'firmwareTypeName',
4925              '1.3.6.1.4.1.674.10892.1.300.60.1.11.1' => 'firmwareVersionName',
4926             );
4927
4928         my $firmwareTable = '1.3.6.1.4.1.674.10892.1.300.60.1';
4929         my $result = $snmp_session->get_table(-baseoid => $firmwareTable);
4930
4931         # Some don't have this OID, this is ok
4932         if (!defined $result) {
4933             return;
4934         }
4935
4936         @snmp_output = @{ get_snmp_output($result, \%fw_oid) };
4937     }
4938     else {
4939         %nrpe_output = %{ run_omreport_info("$omopt_chassis info") };
4940     }
4941
4942     my %fw_type  # Firmware types
4943       = (
4944          1  => 'other',                              # other than following values
4945          2  => 'unknown',                            # unknown
4946          3  => 'systemBIOS',                         # System BIOS
4947          4  => 'embeddedSystemManagementController', # Embedded System Management Controller
4948          5  => 'powerSupplyParallelingBoard',        # Power Supply Paralleling Board
4949          6  => 'systemBackPlane',                    # System (Primary) Backplane
4950          7  => 'powerVault2XXSKernel',               # PowerVault 2XXS Kernel
4951          8  => 'powerVault2XXSApplication',          # PowerVault 2XXS Application
4952          9  => 'frontPanel',                         # Front Panel Controller
4953          10 => 'baseboardManagementController',      # Baseboard Management Controller
4954          11 => 'hotPlugPCI',                         # Hot Plug PCI Controller
4955          12 => 'sensorData',                         # Sensor Data Records
4956          13 => 'peripheralBay',                      # Peripheral Bay Backplane
4957          14 => 'secondaryBackPlane',                 # Secondary Backplane for ESM 2 systems
4958          15 => 'secondaryBackPlaneESM3And4',         # Secondary Backplane for ESM 3 and 4 systems
4959          16 => 'rac',                                # Remote Access Controller
4960          17 => 'iDRAC',                              # Integrated Dell Remote Access Controller
4961          19 => 'unifiedServerConfigurator',          # Unified Server Configurator
4962          20 => 'lifecycleController',                # Lifecycle Controller
4963         );
4964
4965
4966     if ($snmp) {
4967         foreach my $out (@snmp_output) {
4968             if ($fw_type{$out->{firmwareType}} eq 'baseboardManagementController') {
4969                 $sysinfo{'bmc'} = 1;
4970                 $sysinfo{'bmc_fw'} = $out->{firmwareVersionName};
4971             }
4972             elsif ($fw_type{$out->{firmwareType}} =~ m{\A rac|iDRAC \z}xms) {
4973                 my $name = $out->{firmwareTypeName}; $name =~ s/\s//gxms;
4974                 $sysinfo{'rac'} = 1;
4975                 $sysinfo{'rac_name'} = $name;
4976                 $sysinfo{'rac_fw'} = $out->{firmwareVersionName};
4977             }
4978         }
4979     }
4980     else {
4981         foreach my $key (keys %nrpe_output) {
4982             next if !defined $nrpe_output{$key};
4983             if ($key eq 'BMC Version' or $key eq 'Baseboard Management Controller Version') {
4984                 $sysinfo{'bmc'} = 1;
4985                 $sysinfo{'bmc_fw'} = $nrpe_output{$key};
4986             }
4987             elsif ($key =~ m{\A (i?DRAC)\s*(\d?)\s+Version}xms) {
4988                 my $name = "$1$2";
4989                 $sysinfo{'rac'} = 1;
4990                 $sysinfo{'rac_fw'} = $nrpe_output{$key};
4991                 $sysinfo{'rac_name'} = $name;
4992             }
4993         }
4994     }
4995
4996     return;
4997 }
4998
4999
5000
5001 #=====================================================================
5002 # Main program
5003 #=====================================================================
5004
5005 # Here we do the actual checking of components
5006 # Check global status if applicable
5007 if ($global) {
5008     $globalstatus = check_global();
5009 }
5010
5011 # Do multiple selected checks
5012 if ($check{storage})     { check_storage();       }
5013 if ($check{memory})      { check_memory();        }
5014 if ($check{fans})        { check_fans();          }
5015 if ($check{power})       { check_powersupplies(); }
5016 if ($check{temp})        { check_temperatures();  }
5017 if ($check{cpu})         { check_processors();    }
5018 if ($check{voltage})     { check_volts();         }
5019 if ($check{batteries})   { check_batteries();     }
5020 if ($check{amperage})    { check_pwrmonitoring(); }
5021 if ($check{intrusion})   { check_intrusion();     }
5022 if ($check{sdcard})      { check_sdcard();        }
5023 if ($check{alertlog})    { check_alertlog();      }
5024 if ($check{esmlog})      { check_esmlog();        }
5025 if ($check{esmhealth})   { check_esmlog_health(); }
5026
5027
5028 #---------------------------------------------------------------------
5029 # Finish up
5030 #---------------------------------------------------------------------
5031
5032 # Counter variable
5033 %nagios_alert_count
5034   = (
5035      'OK'       => 0,
5036      'WARNING'  => 0,
5037      'CRITICAL' => 0,
5038      'UNKNOWN'  => 0,
5039     );
5040
5041 # Get system information
5042 get_sysinfo();
5043
5044 # Get firmware info if requested via option
5045 if ($opt{okinfo} >= 1) {
5046     get_firmware_info();
5047 }
5048
5049 # Close SNMP session
5050 if ($snmp) {
5051     $snmp_session->close;
5052 }
5053
5054 # Print messages
5055 if ($opt{debug}) {
5056     # finding the mode of operation
5057     my $mode = 'local';
5058     if ($snmp) {
5059         # Setting the domain (IP version and transport protocol)
5060         my $transport = $opt{tcp} ? 'TCP' : 'UDP';
5061         my $ipversion = $opt{ipv6} ? 'IPv6' : 'IPv4';
5062         $mode = "SNMPv$opt{protocol} $transport/$ipversion";
5063     }
5064
5065     print "   System:      $sysinfo{model}$sysinfo{rev}";
5066     print q{ } x (25 - length "$sysinfo{model}$sysinfo{rev}"), "OMSA version:    $sysinfo{om}\n";
5067     print "   ServiceTag:  $sysinfo{serial}";
5068     print q{ } x (25 - length $sysinfo{serial}), "Plugin version:  $VERSION\n";
5069     print "   BIOS/date:   $sysinfo{bios} $sysinfo{biosdate}";
5070     print q{ } x (25 - length "$sysinfo{bios} $sysinfo{biosdate}"), "Checking mode:   $mode\n";
5071     if ($#report_storage >= 0) {
5072         print "-----------------------------------------------------------------------------\n";
5073         print "   Storage Components                                                        \n";
5074         print "=============================================================================\n";
5075         print "  STATE  |    ID    |  MESSAGE TEXT                                          \n";
5076         print "---------+----------+--------------------------------------------------------\n";
5077         foreach (@report_storage) {
5078             my ($msg, $level, $nexus) = @{$_};
5079             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | "
5080               . q{ } x (8 - length $nexus) . "$nexus | $msg\n";
5081             $nagios_alert_count{$reverse_exitcode{$level}}++;
5082         }
5083     }
5084     if ($#report_chassis >= 0) {
5085         print "-----------------------------------------------------------------------------\n";
5086         print "   Chassis Components                                                        \n";
5087         print "=============================================================================\n";
5088         print "  STATE  |  ID  |  MESSAGE TEXT                                              \n";
5089         print "---------+------+------------------------------------------------------------\n";
5090         foreach (@report_chassis) {
5091             my ($msg, $level, $nexus) = @{$_};
5092             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | "
5093               . q{ } x (4 - length $nexus) . "$nexus | $msg\n";
5094             $nagios_alert_count{$reverse_exitcode{$level}}++;
5095         }
5096     }
5097     if ($#report_other >= 0) {
5098         print "-----------------------------------------------------------------------------\n";
5099         print "   Other messages                                                            \n";
5100         print "=============================================================================\n";
5101         print "  STATE  |  MESSAGE TEXT                                                     \n";
5102         print "---------+-------------------------------------------------------------------\n";
5103         foreach (@report_other) {
5104             my ($msg, $level, $nexus) = @{$_};
5105             print q{ } x (8 - length $reverse_exitcode{$level}) . "$reverse_exitcode{$level} | $msg\n";
5106             $nagios_alert_count{$reverse_exitcode{$level}}++;
5107         }
5108     }
5109 }
5110 else {
5111     my $c = 0;  # counter to determine linebreaks
5112
5113     # Run through each message, sorted by severity level
5114   ALERT:
5115     foreach (sort {$a->[1] < $b->[1]} (@report_storage, @report_chassis, @report_other)) {
5116         my ($msg, $level, $nexus) = @{ $_ };
5117         next ALERT if $level == $E_OK;
5118
5119         if (defined $opt{only}) {
5120             # If user wants only critical alerts
5121             next ALERT if ($opt{only} eq 'critical' and $level == $E_WARNING);
5122
5123             # If user wants only warning alerts
5124             next ALERT if ($opt{only} eq 'warning' and $level == $E_CRITICAL);
5125         }
5126
5127         # Prefix with service tag if specified with option '-i|--info'
5128         if ($opt{info}) {
5129             if (defined $opt{htmlinfo}) {
5130                 $msg = '[<a target="_blank" href="' . warranty_url($sysinfo{serial})
5131                   . "\">$sysinfo{serial}</a>] " . $msg;
5132             }
5133             else {
5134                 $msg = "[$sysinfo{serial}] " . $msg;
5135             }
5136         }
5137
5138         # Prefix with nagios level if specified with option '--state'
5139         $msg = $reverse_exitcode{$level} . ": $msg" if $opt{state};
5140
5141         # Prefix with one-letter nagios level if specified with option '--short-state'
5142         $msg = (substr $reverse_exitcode{$level}, 0, 1) . ": $msg" if $opt{shortstate};
5143
5144         ($c++ == 0) ? print $msg : print $linebreak, $msg;
5145
5146         $nagios_alert_count{$reverse_exitcode{$level}}++;
5147     }
5148 }
5149
5150 # Determine our exit code
5151 $exit_code = $E_OK;
5152 $exit_code = $E_UNKNOWN  if $nagios_alert_count{'UNKNOWN'} > 0;
5153 $exit_code = $E_WARNING  if $nagios_alert_count{'WARNING'} > 0;
5154 $exit_code = $E_CRITICAL if $nagios_alert_count{'CRITICAL'} > 0;
5155
5156 # Global status via SNMP.. extra safety check
5157 if ($globalstatus != $E_OK && $exit_code == $E_OK && !defined $opt{only}) {
5158     print "OOPS! Something is wrong with this server, but I don't know what. ";
5159     print "The global system health status is $reverse_exitcode{$globalstatus}, ";
5160     print "but every component check is OK. This may be a bug in the Nagios plugin, ";
5161     print "please file a bug report.\n";
5162     exit $E_UNKNOWN;
5163 }
5164
5165 # Print OK message
5166 if ($exit_code == $E_OK && defined $opt{only} && $opt{only} !~ m{\A critical|warning|chassis \z}xms && !$opt{debug}) {
5167     my %okmsg
5168       = ( 'storage'     => "STORAGE OK - $count{pdisk} physical drives, $count{vdisk} logical drives",
5169           'fans'        => $count{fan} == 0 && $blade ? 'OK - blade system with no fan probes' : "FANS OK - $count{fan} fan probes checked",
5170           'temp'        => "TEMPERATURES OK - $count{temp} temperature probes checked",
5171           'memory'      => "MEMORY OK - $count{dimm} memory modules, $count{mem} MB total memory",
5172           'power'       => $count{power} == 0 ? 'OK - no instrumented power supplies found' : "POWER OK - $count{power} power supplies checked",
5173           'cpu'         => "PROCESSORS OK - $count{cpu} processors checked",
5174           'voltage'     => "VOLTAGE OK - $count{volt} voltage probes checked",
5175           'batteries'   => $count{bat} == 0 ? 'OK - no batteries found' : "BATTERIES OK - $count{bat} batteries checked",
5176           'amperage'    => $count{amp} == 0 ? 'OK - no power monitoring probes found' : "AMPERAGE OK - $count{amp} amperage (power monitoring) probes checked",
5177           'intrusion'   => $count{intr} == 0 ? 'OK - no intrusion detection probes found' : "INTRUSION OK - $count{intr} intrusion detection probes checked",
5178           'alertlog'    => $snmp ? 'OK - not supported via snmp' : "OK - Alert Log content: $count{alert}{Ok} ok, $count{alert}{'Non-Critical'} warning and $count{alert}{Critical} critical",
5179           'esmlog'      => "OK - ESM Log content: $count{esm}{Ok} ok, $count{esm}{'Non-Critical'} warning and $count{esm}{Critical} critical",
5180           'esmhealth'   => "ESM LOG OK - less than 80% used",
5181           'sdcard'      => "SD CARDS OK - $count{sd} SD cards installed",
5182         );
5183
5184     print $okmsg{$opt{only}};
5185
5186     # show blacklisted components
5187     if ($opt{show_blacklist} and %blacklist) {
5188         my @blstr = ();
5189         foreach (keys %blacklist) {
5190             push @blstr, "$_=" . join ',', @{ $blacklist{$_} };
5191         }
5192         print $linebreak;
5193         print "----- BLACKLISTED: " . join '/', @blstr;
5194     }
5195 }
5196 elsif ($exit_code == $E_OK && !$opt{debug}) {
5197     if (defined $opt{htmlinfo}) {
5198         printf q{OK - System: '<a target="_blank" href="%s">%s%s</a>', SN: '<a target="_blank" href="%s">%s</a>'},
5199           documentation_url($sysinfo{model}), $sysinfo{model}, $sysinfo{rev},
5200             warranty_url($sysinfo{serial}), $sysinfo{serial};
5201     }
5202     else {
5203         printf q{OK - System: '%s%s', SN: '%s'},
5204           $sysinfo{model}, $sysinfo{rev}, $sysinfo{serial};
5205     }
5206
5207     if ($check{memory}) {
5208         my $unit = 'MB';
5209         if ($count{mem} >= 1024) {
5210             $count{mem} /= 1024;
5211             $unit = 'GB';
5212         }
5213         printf ', %d %s ram (%d dimms)', $count{mem}, $unit, $count{dimm};
5214     }
5215     else {
5216         print ', not checking memory';
5217     }
5218
5219     if ($check{storage}) {
5220         printf ', %d logical drives, %d physical drives',
5221           $count{vdisk}, $count{pdisk};
5222     }
5223     else {
5224         print ', not checking storage';
5225     }
5226
5227     # show blacklisted components
5228     if ($opt{show_blacklist} and %blacklist) {
5229         my @blstr = ();
5230         foreach (keys %blacklist) {
5231             push @blstr, "$_=" . join ',', @{ $blacklist{$_} };
5232         }
5233         print $linebreak;
5234         print "----- BLACKLISTED: " . join '/', @blstr;
5235     }
5236
5237     if ($opt{okinfo} >= 1) {
5238         print $linebreak;
5239         printf q{----- BIOS='%s %s'}, $sysinfo{bios}, $sysinfo{biosdate};
5240
5241         if ($sysinfo{rac}) {
5242             printf q{, %s='%s'}, $sysinfo{rac_name}, $sysinfo{rac_fw};
5243         }
5244         if ($sysinfo{bmc}) {
5245             printf q{, BMC='%s'}, $sysinfo{bmc_fw};
5246         }
5247     }
5248
5249     if ($opt{okinfo} >= 2) {
5250         if ($check{storage}) {
5251             my @storageprint = ();
5252             foreach my $id (sort keys %{ $sysinfo{controller} }) {
5253                 chomp $sysinfo{controller}{$id}{driver};
5254                 my $msg = sprintf q{----- Ctrl %s [%s]: Fw='%s', Dr='%s'},
5255                   $sysinfo{controller}{$id}{id}, $sysinfo{controller}{$id}{name},
5256                     $sysinfo{controller}{$id}{firmware}, $sysinfo{controller}{$id}{driver};
5257                 if (defined $sysinfo{controller}{$id}{storport}) {
5258                     $msg .= sprintf q{, Storport: '%s'}, $sysinfo{controller}{$id}{storport};
5259                 }
5260                 push @storageprint, $msg;
5261             }
5262             foreach my $id (sort keys %{ $sysinfo{enclosure} }) {
5263                 push @storageprint, sprintf q{----- Encl %s [%s]: Fw='%s'},
5264                   $sysinfo{enclosure}{$id}->{id}, $sysinfo{enclosure}{$id}->{name},
5265                     $sysinfo{enclosure}{$id}->{firmware};
5266             }
5267
5268             # print stuff
5269             foreach my $line (@storageprint) {
5270                 print $linebreak, $line;
5271             }
5272         }
5273     }
5274
5275     if ($opt{okinfo} >= 3) {
5276         print "$linebreak----- OpenManage Server Administrator (OMSA) version: '$sysinfo{om}'";
5277     }
5278
5279 }
5280 else {
5281     if ($opt{extinfo}) {
5282         print $linebreak;
5283         if (defined $opt{htmlinfo}) {
5284             printf '------ SYSTEM: <a target="_blank" href="%s">%s%s</a>, SN: <a target="_blank" href="%s">%s</a>',
5285               documentation_url($sysinfo{model}), $sysinfo{model}, $sysinfo{rev},
5286                 warranty_url($sysinfo{serial}), $sysinfo{serial};
5287         }
5288         else {
5289             printf '------ SYSTEM: %s%s, SN: %s',
5290               $sysinfo{model}, $sysinfo{rev}, $sysinfo{serial};
5291         }
5292     }
5293     if (defined $opt{postmsg}) {
5294         my $post = undef;
5295         if (-f $opt{postmsg}) {
5296             open my $POST, '<', $opt{postmsg}
5297               or ( print $linebreak
5298                    and print "ERROR: Couldn't open post message file $opt{postmsg}: $!\n"
5299                    and exit $E_UNKNOWN );
5300             $post = <$POST>;
5301             close $POST;
5302             chomp $post;
5303         }
5304         else {
5305             $post = $opt{postmsg};
5306         }
5307         if (defined $post) {
5308             print $linebreak;
5309             $post =~ s{[%]s}{$sysinfo{serial}}gxms;
5310             $post =~ s{[%]m}{$sysinfo{model}$sysinfo{rev}}gxms;
5311             $post =~ s{[%]b}{$sysinfo{bios}}gxms;
5312             $post =~ s{[%]d}{$sysinfo{biosdate}}gxms;
5313             $post =~ s{[%]o}{$sysinfo{osname}}gxms;
5314             $post =~ s{[%]r}{$sysinfo{osver}}gxms;
5315             $post =~ s{[%]p}{$count{pdisk}}gxms;
5316             $post =~ s{[%]l}{$count{vdisk}}gxms;
5317             $post =~ s{[%]n}{$linebreak}gxms;
5318             $post =~ s{[%]{2}}{%}gxms;
5319             print $post;
5320         }
5321     }
5322 }
5323
5324 # Reset the WARN signal
5325 $SIG{__WARN__} = 'DEFAULT';
5326
5327 # Print any perl warnings that have occured
5328 if (@perl_warnings) {
5329     foreach (@perl_warnings) {
5330         chop @$_;
5331         print "${linebreak}INTERNAL ERROR: @$_";
5332     }
5333     $exit_code = $E_UNKNOWN;
5334 }
5335
5336 # Print performance data
5337 if (defined $opt{perfdata} && !$opt{debug} && @perfdata) {
5338     my $lb = $opt{perfdata} eq 'multiline' ? "\n" : q{ };  # line break for perfdata
5339     print q{|};
5340
5341     # Sort routine for performance data
5342     sub perfsort {
5343         my %order = ( 'T' => 0, 'W' => 1, 'A' => 2, 'V' => 3, 'F' => 4, 'E' => 5, );
5344
5345         # sort in this order:
5346         #  1. the type according to the hash "order" above
5347         #  2. the id (index) numerically
5348         #  3. the id (index) alphabetically
5349         #  4. the label
5350         return $order{$a->{type}} cmp $order{$b->{type}} ||
5351           ($a->{id} =~ m{\A\d+\z}xms and $a->{id} <=> $b->{id}) ||
5352             ($a->{id} !~ m{\A\d+\z}xms and $a->{id} cmp $b->{id}) ||
5353               $a->{label} cmp $b->{label};
5354     }
5355
5356     # LEGACY sort routine for performance data
5357     sub perfsort_legacy {
5358         my %order = ( fan => 0, pwr => 1, tem => 2, enc => 3, );
5359         return ($order{(substr $a->{legacy}, 0, 3)} cmp $order{(substr $b->{legacy}, 0, 3)}) ||
5360           $a->{legacy} cmp $b->{legacy};
5361     }
5362
5363     # Print performance data sorted
5364     if ($opt{legacy_perfdata}) {
5365         my $type = $opt{perfdata} eq 'minimal' ? 'mini' : 'legacy';
5366         print join $lb, map { "$_->{$type}=$_->{value};$_->{warn};$_->{crit}" } sort perfsort_legacy @perfdata;
5367     }
5368     else {
5369         if ($opt{perfdata} eq 'minimal') {
5370             print join $lb, map { "$_->{type}$_->{id}=$_->{value}$_->{unit};$_->{warn};$_->{crit}" } sort perfsort @perfdata;
5371         }
5372         else {
5373             print join $lb, map { "$_->{type}$_->{id}_$_->{label}=$_->{value}$_->{unit};$_->{warn};$_->{crit}" } sort perfsort @perfdata;
5374         }
5375     }
5376 }
5377
5378 # Wrapping up and finishing
5379 if ($opt{debug}) {
5380    # Exit with value 3 (unknown) if debug
5381     exit $E_UNKNOWN;
5382 }
5383 else {
5384     # Print a linebreak at the end if we have a TTY
5385     isatty(*STDOUT) && print "\n";
5386
5387     # Exit with proper exit code
5388     exit $exit_code;
5389 }