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