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