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