--- /dev/null
+#!/usr/bin/perl
+#
+# checks the status of Adaptec-RAIDs by reading the output of 'arcconf getstatus'
+#
+# Tested with these controllers:
+# * Adaptec 5405
+# * Adaptec 5805
+#
+# $Id: adaptec-check,v 1.1 2012-02-07 13:28:31 hadaq Exp $
+#
+
+use strict;
+use warnings;
+
+use Parse::RecDescent;
+use Data::Dumper;
+
+$::RD_HINT = 1;
+
+my $debug = 0;
+
+my $parser = new Parse::RecDescent(q{
+
+# returns a hash of hashes with controller info:
+# { 'ci' => {
+# 'c_model' => 'blafasel',
+# 'c_drives' => 'n',
+# ...
+# },
+# 'cj' => {
+# 'c_model' => 'bliblabubb',
+# 'c_ports' => 'm',
+# ...
+# }
+# }
+ general_info: controller_num controller_info logic_dev_info phys_dev_info success_msg
+{ $return = \%item; }
+| <error>
+
+controller_num: "Controllers found:" integer
+
+success_msg: "Command completed successfully."
+
+controller_info: delimiter_line "Controller information" delimiter_line controller_details
+
+logic_dev_info: delimiter_line "Logical device information" delimiter_line logic_dev_details(s) | <error>
+
+
+phys_dev_info: delimiter_line /Physical device information/i delimiter_line phys_dev_details | <error>
+
+
+delimiter_line: /-+/
+
+controller_details: c_status c_channel c_model c_serial c_slot(?) c_temp c_mem c_copyback
+ c_bg_cons_check c_failover c_priority c_perfmode c_stayawake c_spinup_int c_spinup_ext
+ c_defunct c_logic_dev c_ssd_cache(?) c_ssd_cache_max(?) c_ncq_status(?)
+ c_version_info c_batt_info
+{ $return = \%item; } | <error>
+
+c_status: "Controller Status" ':' ( status | 'Charging' )
+c_channel: "Channel description" ':' "SAS/SATA"
+c_model: "Controller Model" ':' "Adaptec" ("5405" | "5805")
+c_serial: "Controller Serial Number" ':' serial
+c_slot: "Physical Slot" ':' integer
+c_temp: "Temperature" ':' integer "C/" integer "F (Normal)" { $return = $item[3]; }
+c_mem: "Installed memory" ':' size
+c_copyback: "Copyback" ':' onoff
+c_bg_cons_check: "Background consistency check" ':' onoff
+c_failover: "Automatic Failover" ':' onoff
+c_priority: "Global task priority" ':' "High"
+c_perfmode: "Performance Mode" ':' "Default/Dynamic"
+c_stayawake: "Stayawake period" ':' onoff
+c_spinup_int: "Spinup limit internal drives" ':' integer
+c_spinup_ext: "Spinup limit external drives" ':' integer
+c_defunct: "Defunct disk drive count" ':' integer
+c_logic_dev: "Logical devices/Failed/Degraded" ':' m|\d+/\d/\d|
+c_ssd_cache: "SSDs assigned to MaxIQ Cache pool" ':' integer
+c_ssd_cache_max: "Maximum SSDs allowed in MaxIQ Cache pool" ':' integer
+c_ncq_status: "NCQ status" ':' onoff
+
+c_version_info: delimiter_line "Controller Version Information" delimiter_line
+ c_version_bios c_version_firmware c_version_driver c_version_bootflash
+
+c_version_bios: "BIOS" ':' version
+c_version_firmware: "Firmware" ':' version
+c_version_driver: "Driver" ':' version
+c_version_bootflash: "Boot Flash" ':' version
+
+# battery might be installed or not
+c_batt_info: delimiter_line "Controller Battery Information" delimiter_line
+ c_batt_status (c_batt_over_temp c_batt_capacity c_batt_time_remain)(?) { $return = \%item; }
+
+c_batt_status: "Status" ':' ( status | "Charging" ) { $return = $item[3]; }
+c_batt_over_temp: "Over temperature" ':' yesno
+c_batt_capacity: "Capacity remaining" ':' integer "percent" { $return = $item[3]; }
+c_batt_time_remain: "Time remaining (at current draw)" ':' integer "days," integer "hours," integer "minutes"
+{ $return = (((($item[3]*24) + $item[5]) * 60) + $item[7])."m"; } # calculate time in minutes
+
+#
+# logical device info section
+#
+
+logic_dev_details: l_devnum l_name l_raidlevel l_status l_size l_stripe(?) l_readcache_mode
+ l_maxiq_pref(?) l_maxiq(?) l_writecache_mode l_writecache_setting
+ l_partitioned l_hotspare_protect l_hotspare_global(?) l_bootable l_stripes_failed
+ l_power l_power_slow(?) l_power_off(?) l_power_verify(?) l_power_state(?)
+ l_segment_info
+{ $return = \%item; } | <error>
+
+
+l_segment_info: delimiter_line "Logical device segment information" delimiter_line l_segment_details(s) | <error>
+
+
+l_devnum: "Logical device number" integer
+
+# name might be empty, therefore we look for the next newline:
+l_name: "Logical device name" ':' <skip:'[ \t]*'>/.*\n/
+{ $return = $item[4]; chomp $return } # cut off the trailing newline
+
+l_raidlevel: "RAID level" ':' raidlevel
+l_status: "Status of logical device" ':' status
+l_size: "Size" ':' size
+l_stripe: "Stripe-unit size" ':' size
+l_readcache_mode: "Read-cache mode" ':' onoff
+l_maxiq_pref: "MaxIQ preferred cache setting" ':' onoff
+l_maxiq: "MaxIQ cache setting" ':' onoff
+l_writecache_mode: "Write-cache mode" ':' writecache
+l_writecache_setting: "Write-cache setting" ':' writecache
+l_partitioned: "Partitioned" ':' yesno
+l_hotspare_protect: "Protected by Hot-Spare" ':' yesno
+l_hotspare_global: "Global Hot-Spare" ':' /\d,\d+/
+l_bootable: "Bootable" ':' yesno
+l_stripes_failed: "Failed stripes" ':' yesno
+l_power: "Power settings" ':' onoff
+l_power_slow: "Slow down after(Minutes)" ':' time
+l_power_off: "Power off after(Minutes)" ':' time
+l_power_verify: "Verify after(Hours)" ':' time
+l_power_state: "Power State" ':' ("Active" | "Standby" | "Powered Off")
+
+
+l_segment_details: "Segment" integer ':' l_segment_status l_segment_pos <skip:'[ \t]*'>disk_serial(?) { $return = \%item; }
+l_segment_status: 'Inconsistent' | "Present" | "Rebuilding" | "Spare"
+l_segment_pos: /\(\d,\d+\)/
+
+
+phys_dev_details: p_hd_details(s) p_service_details { $return = \%item; } | <error>
+
+p_hd_details: p_devnum p_type p_state p_supported p_speed(?) p_channel (p_location p_esd)(?) p_vendor
+ p_model p_firmware p_serial(?) p_size p_writecache p_fru p_smart p_smartwarn (p_pwrstate
+ p_pwrstates p_ssd(?) p_maxiq_capable(?) p_maxiq(?) p_ncq)(?) { $return = \%item; } | <error>
+
+p_service_details: p_devnum p_type p_channel p_enclosure p_esd_type p_vendor p_model p_firmware
+ p_esd_status p_fan_status(s?) p_psu_status(s?) p_temp { $return = \%item; } | <error>
+
+p_devnum: "Device" /\#\d+/
+p_type: "Device is" /an?/ ("Hard drive" | "Enclosure services device" )
+p_state: "State" ':' ( 'Failed' | "Hot Spare" | 'Online'
+ | 'Ready' | 'Rebuilding' )
+p_supported: "Supported" ':' yesno
+p_speed: "Transfer Speed" ':' "SATA" float "Gb/s" { $return = $item[4]; }
+p_channel: "Reported Channel,Device(T:L)" ':' logic_loc
+p_location: "Reported Location" ':' "Enclosure" /\d,/ "Slot" /\d+/
+p_esd: "Reported ESD(T:L)" ':' logic_loc
+p_vendor: "Vendor" ':' ( "Hitachi" | "LSI CORP" | "LSILOGIC"
+ | "WDC" | '*MISSING*' )(?)
+p_model: "Model" ':' ( hd_model | /SASX36 A\.[01]/ | "SAS2X36" )(?)
+p_firmware: "Firmware" ':' ( /\d+\.\d+([A-Z]\d+)?/ | "SN06"
+ | "JKAOA28A" | integer )(?)
+p_serial: "Serial number" ':' disk_serial
+p_size: "Size" ':' size
+p_writecache: "Write Cache" ':' writecache
+p_fru: "FRU" ':' "None"
+p_smart: "S.M.A.R.T." ':' yesno
+p_smartwarn: "S.M.A.R.T. warnings" ':' integer
+p_pwrstate: "Power State" ':' ("Full rpm" | "Powered off")
+p_pwrstates: "Supported Power States" ':' /Full rpm,Powered off(,Reduced rpm)?/
+p_ssd: "SSD" ':' yesno
+p_maxiq_capable: "MaxIQ Cache Capable" ':' yesno
+p_maxiq: "MaxIQ Cache Assigned" ':' yesno
+p_ncq: "NCQ status" ':' onoff
+
+p_enclosure: "Enclosure ID" ':' integer
+p_esd_type: "Type" ':' "SES2"
+p_esd_status: "Status of Enclosure services device"
+p_fan_status: "Fan" integer "status" ':' ('Unknown' | 'Critical' | 'Not available' )
+p_psu_status: "Power supply" integer "status" ':' 'Unknown'
+p_temp: "Temperature" ':' ('Abnormal' | 'Normal')
+
+
+integer: /-?\d+/
+logic_loc: /\d,\d+\(\d+:\d\)/
+raidlevel: "0" | "1" | "6 Reed-Solomon" | "Simple_volume"
+
+hd_model: /WD\d+[A-Z]+-\d/ | /ST\d{6,9}[AN]S/ | "HUA722020ALA330"
+
+size: integer /[KM]B/ { $return = join(' ',@item) }
+status: "Optimal" | "Impacted" | "Not Installed" | "Failed" | "Suboptimal, Fault Tolerant" | <error>
+string: /\S+/
+undef: '-'
+version: /\d\.\d-\d/ /\(\d+\)/
+writecache: /Enabled \(write-back\)( when protected by battery(\/ZMM)?)?/ | 'Disabled (write-through)' | 'Unknown'
+
+float: /\d+\.\d+/
+onoff: /on/i | /off/i | /enabled/i | /disabled/i | undef
+yesno: /yes/i | /no/i
+date: /\d{2}-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{4}/ | 'xx-xxx-xxxx'
+time: /\d+[hm]/
+
+serial: /[0-9A-F]{11}/
+
+disk_size: float 'GB' { $return = "$item[1]" }| undef
+disk_blocks: integer | undef
+disk_serial: string | undef { $return = 'Unknown' }
+
+});
+
+
+
+#### execute command and parse output
+
+
+my $cmd = "arcconf getconfig 1";
+my $input = `$cmd`;
+
+exit 42 if $input =~ /No controller found/;
+
+my $info = $parser->general_info(\$input);
+
+if ($input =~ /\S/) {
+ print STDERR "Unparsed remainder of '$cmd':\n$input\n";
+}
+
+print Dumper($info) if $debug;
+
+my $c_status = $info->{'controller_info'}->{'c_status'};
+
+# check for controller status:
+if ( $c_status ne "Optimal") {
+ print STDERR "Controller status: '$c_status'\n";
+}
+
+# check for battery status:
+my $c_batt_status = $info->{'controller_info'}->{'c_batt_info'}->{'c_batt_status'};
+
+if ( $c_batt_status !~ /Optimal|Charging/ ) {
+ print STDERR "Controller battery status: '$c_batt_status'\n";
+}
+
+foreach my $u (@{$info->{'logic_dev_info'}}) {
+ if ($u->{'l_status'} ne "Optimal") {
+ print STDERR "Unit $u->{'l_devnum'} ('$u->{'l_name'}') status is $u->{'l_status'}\n";
+}
+
+foreach my $p (@{$u->{'l_segment_info'}}) {
+ if ($p->{'l_segment_status'} !~ /Present|Spare/ ) {
+ print STDERR "Unit $u->{'l_devnum'} ('$u->{'l_name'}'), segment $p->{'l_segment_pos'} status is $p->{'l_segment_status'}\n";
+ }
+
+}
+
+foreach my $p (@{$info->{'phys_dev_info'}->{'p_hd_details(s)'}}) {
+
+ print "$p->{'p_type'} $p->{'p_devnum'} $p->{'p_state'}\n" if $debug;
+
+ if ($p->{'p_state'} !~ /Online|Hot Spare|Ready/ ) {
+ print STDERR "$p->{'p_type'} $p->{'p_devnum'} (Serial: ".($p->{'p_serial'} or 'unknown')
+ .", Model: ".($p->{'p_model'}[0] or 'unknown').") status is '$p->{'p_state'}'.\n";
+ }
+
+ #TODO: also check for p_smart or p_smartwarn
+}
+
+}