#!/usr/bin/perl -w
#
# $Id: xserve-raid-status.in,v 1.7 2005/12/05 22:01:35 rader Exp $
#
# exit status...
#    0 for okay
#    1 for building or rebuilding
#    2 for degraded
#    3 for dead
#    4 for standby power mode
#    5 for general failure (syntax error or comm failed)
#

use strict;
use XML::Parser;

my $prefix = "/usr/local";
my $lib_dir = "$prefix/lib/xserve-raid-tools";
my $etc_dir = "$prefix/etc";
my $conf_file = "xserve-raid-tools.conf";
my $status_template = "xserve-status-page.template";
my $port = "80";
my $debug = 0;
my $read_from_files = 0;
my $input_file = '';
my $exit_status = 0;
my $mode = 'COMBINED';

my $addr;
my $passwd;
my ($loc,$type,$model,$size,$status,$id);
my $xml;
my %raids;
my %raid_attrs;
my %raid_members;
my %rebuilding_disks;
my %disks;
my %disk_attrs;
my %cf;

#------------------------------------------------------------------------------

&read_conf_file;
$addr = $cf{'addr'};
$passwd = $cf{'passwd'};

#------------------------------------------------------------------------------

while ( $ARGV[0] && $ARGV[0] =~ /^-/ ) {
  if ( $ARGV[0] eq '-d' || $ARGV[0] eq '--debug' ) { 
    $debug++;
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-f' || $ARGV[0] eq '--file' ) { 
    $read_from_files = 1;
    shift @ARGV;
    if ( ! $ARGV[0] ) { &usage; exit 5; }
    $input_file = $ARGV[0];
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-a' || $ARGV[0] eq '--address' ) { 
    shift @ARGV;
    if ( ! $ARGV[0] ) { &usage; exit 5; }
    $addr = $ARGV[0];
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-p' || $ARGV[0] eq '--passwd' ) {
    shift @ARGV;
    if ( ! $ARGV[0] ) { &usage; exit 5; }
    $passwd = $ARGV[0];
    shift @ARGV;
    next;
  }
  if ( $ARGV[0] eq '-l' || $ARGV[0] eq '--list' ) {
    if ( ! $ARGV[0] ) { &usage; exit 5; }
    shift @ARGV;
    CASE: {
      if ( $ARGV[0] =~ /^l/i ) { $mode = 'LOGICAL'; last; }
      if ( $ARGV[0] =~ /^r/i ) { $mode = 'LOGICAL'; last; }
      if ( $ARGV[0] =~ /^p/i ) { $mode = 'PHYSICAL'; last; }
      if ( $ARGV[0] =~ /^d/i ) { $mode = 'PHYSICAL'; last; }
      if ( $ARGV[0] =~ /^c/i ) { $mode = 'COMBINED'; last; }
      if ( $ARGV[0] =~ /^b/i ) { $mode = 'COMBINED'; last; }
      &usage;
      exit 5;
    }
    shift @ARGV;
    next;
  }
  &usage;
  exit 5;
}
if ( $ARGV[0] ) { 
  &usage;
  exit 5;
}

#------------------------------------------------------------------------------

my $p = new XML::Parser(Style=>'Tree');

#------------------------------------------------------------------------------

&parse_page1('top','lhs');
&parse_page1('bottom','rhs');
&parse_page16('top','lhs');
&parse_page16('bottom','rhs');

#------------------------------------------------------------------------------

print <<EOT;

Location      Type        Make and Model              Capacity  Status
----------------------------------------------------------------------------
EOT

format output =  
@<<<<<<<<<<<  @<<<<<<<<<  @<<<<<<<<<<<<<<<<<<<<<<<  @>>>>>>>>>  @<<<<<<<<<<<
$loc,$type,$model,$size,$status
.
$~ = "output";

#------------------------------------------------------------------------------

if ( $mode eq 'PHYSICAL' ) {
  for my $i (sort(keys %disks)) {
    &write_disk($i,'');
  }
} else { 
  for my $i (sort keys %raids ) {
    &write_raid($i);
    if ( $mode eq 'COMBINED' ) {
      for my $j (sort keys %{$raid_members{$i}} ) {
        &write_disk($j,'  ');
      }
    }
  }
}
  
print "\n";

exit $exit_status;

#==============================================================================

sub parse_page1 {
  my ($controller,$side) = @_;
  my $raid_num = -1;
  my $prev = '';

  #------------------------------------------------------------------
  open(IN,"<$lib_dir/$status_template");
  open(OUT,">/tmp/xserve-raid-query.$$");
  while(<IN>) { 
    $_ =~ s/_ADDR_/$addr/;
    $_ =~ s/_CONTROLLER_/$controller/;
    $_ =~ s/_PAGE_ID_/1/;
    $_ =~ s/_PASSWD_/$passwd/;
    print OUT $_;
  }
  close(OUT);
  close(IN);

  #------------------------------------------------------------------
  if ( $read_from_files ) {
    if ( $debug ) { 
      print "** DEBUG QUERY ** $controller ($side) ARRAYS ** < $input_file.$side.pg1 **\n";
    }
    open(IN,"<$input_file.$side.pg1");
  } else {
    if ( $debug ) { 
      print "** DEBUG QUERY ** $controller ($side) ARRAYS ** /bin/nc $addr $port < /tmp/xserve-raid-query.$$  **\n";
    }
    open(IN,"/bin/nc $addr $port < /tmp/xserve-raid-query.$$ |");
  }

  #------------------------------------------------------------------
  while(<IN>) { 
    if ( $debug > 1 ) { print $_; }
    if ( $_ =~ /^<\?xml/ ) { 
      $xml = $_; last;  
    }
  }
  while(<IN>) {
    $xml .= $_;
    if ( $debug > 1 ) { print $_; }
  }

  #------------------------------------------------------------------
  unlink("/tmp/xserve-raid-query.$$");
  if ( ! close(IN) ) { 
    print "could not contact Xserve RAID at $addr\n";
    exit 5;
  }

  #------------------------------------------------------------------
  my $tree = $p->parse($xml);

  #------------------------------------------------------------------
  if ( $tree->[1][4][4][2] eq 'status' ) {
    if ( $debug ) { print "** DEBUG STATUS ** query status is $tree->[1][4][8][2] **\n"; }
    if ( $tree->[1][4][8][2] eq '-6745' ) { 
      print "$addr is in standby power mode\n";
      exit 4;
    }
    if ( $tree->[1][4][8][2] eq '-16' ) { 
      print "$addr: incorrect password\n";
      exit 5;
    }
    print "$addr: unknown status error: \"$tree->[1][4][8][2]\"\n";
    exit 5;
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][48]}; $i += 4) {
    #--------------------
    # attrs...
    my $id = "$side.array$tree->[1][4][8][48][$i][8][2]";
    my $member_offset;
    $raids{$id} = 1;
    $raid_attrs{$id}{'raid-level'} = $tree->[1][4][8][48][$i][24][2];
    $raid_attrs{$id}{'raid-status'} = $tree->[1][4][8][48][$i][48][2];
    if ( $tree->[1][4][8][48][$i][52][2] eq 'sector-capacity' ) {
      # xml for pre-1.5 firmware...
      $raid_attrs{$id}{'sector-capacity'} = $tree->[1][4][8][48][$i][56][2];
      $raid_attrs{$id}{'initialize'} = $tree->[1][4][8][48][$i][72][8][2];
      $raid_attrs{$id}{'add-member'} = $tree->[1][4][8][48][$i][72][16][2];
      $member_offset = 80;
    } else {
      # xml from 1.5 firmware...
      $raid_attrs{$id}{'sector-capacity'} = $tree->[1][4][8][48][$i][64][2];
      $raid_attrs{$id}{'initialize'} = $tree->[1][4][8][48][$i][80][8][2];
      $raid_attrs{$id}{'add-member'} = $tree->[1][4][8][48][$i][80][16][2];
      $member_offset = 88;
    }
    if ( $debug ) { 
      print "** DEBUG DATA  ** found $id\n";
      print "** DEBUG DATA  **   $id raid-level is $raid_attrs{$id}{'raid-level'}\n";
      print "** DEBUG DATA  **   $id raid-status is $raid_attrs{$id}{'raid-status'}\n";
      print "** DEBUG DATA  **   $id sector-capacity is $raid_attrs{$id}{'sector-capacity'}\n"; 
      print "** DEBUG DATA  **   $id initialize is $raid_attrs{$id}{'initialize'}\n";
      print "** DEBUG DATA  **   $id add-member is $raid_attrs{$id}{'add-member'}\n";
    }
    #--------------------
    # members...
    for (my $j = 4; $j < $#{$tree->[1][4][8][48][$i][$member_offset]}; $j += 4) {
      if ( $tree->[1][4][8][48][$i][$member_offset][$j][16][2] ne '0' ) {
        my $member = "$side.slot$tree->[1][4][8][48][$i][$member_offset][$j][16][2]";
        if ( $debug ) { print "** DEBUG DATA  **   $id has member $member\n"; }
        $raid_members{$id}{$member} = 1;
      }
    }
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][56]}; $i += 4) {
    #--------------------
    # slots...
    my $id = "$side.slot$tree->[1][4][8][56][$i][8][2]";
    if ($tree->[1][4][8][56][$i][16][23] eq 'true' ) {
      if ( $debug ) { print "** DEBUG DATA  ** $id is rebuilding\n"; }
      $rebuilding_disks{$id} = 1;
    } else {
      if ( $debug ) { print "** DEBUG DATA  ** $id is not rebuilding\n"; }
    }
  }

}

#==============================================================================

sub parse_page16 {
  my ($controller,$side) = @_;
  my $prev = '';

  #------------------------------------------------------------------
  open(IN,"<$lib_dir/$status_template");
  open(OUT,">/tmp/xserve-raid-query.$$");
  while(<IN>) {
    $_ =~ s/_ADDR_/$addr/;
    $_ =~ s/_CONTROLLER_/$controller/;
    $_ =~ s/_PAGE_ID_/16/;
    $_ =~ s/_PASSWD_/$passwd/;
    print OUT $_;
  }
  close(OUT);
  close(IN);

  #------------------------------------------------------------------
  if ( $read_from_files ) {
    if ( $debug ) {
      print "** DEBUG QUERY ** $controller ($side) DISKS ** < $input_file.$side.pg1 **\n";
    }
    open(IN,"<$input_file.$side.pg16");
  } else {
    if ( $debug ) {
      print "-" x 78, "\n";
      print "** DEBUG QUERY ** $controller ($side) DISKS ** /bin/nc $addr $port < /tmp/xserve-raid-query.$$  **\n";
    }
    open(IN,"/bin/nc $addr $port < /tmp/xserve-raid-query.$$ |");
  }

  #------------------------------------------------------------------
  while(<IN>) {
    if ( $debug > 1 ) { print $_; }
    if ( $_ =~ /^<\?xml/ ) { 
      $xml = $_; last;  
    }
  }
  while(<IN>) {
    $xml .= $_;
    if ( $debug > 1 ) { print $_; }
  }

  #------------------------------------------------------------------
  unlink("/tmp/xserve-raid-query.$$");
  if ( ! close(IN) ) { 
    print "could not contact Xserve RAID at $addr\n";
    exit 3;
  }
  
  #------------------------------------------------------------------
  my $tree = $p->parse($xml);

  #------------------------------------------------------------------
  if ( $tree->[1][4][4][2] eq 'status' ) {
    if ( $debug ) { print "** DEBUG STATUS ** query status is $tree->[1][4][8][2] **\n"; }
    if ( $tree->[1][4][8][2] eq '-6745' ) {
      print "the $controller controller is in standby power mode\n";
      exit 4;
    }
    if ( $tree->[1][4][8][2] eq '-16' ) {
      print "$addr: incorrect password\n";
      exit 5;
    }
    print "$addr: unknown status error: \"$tree->[1][4][8][2]\"\n";
    exit 5;
  }

  #------------------------------------------------------------------
  for (my $i = 4; $i < $#{$tree->[1][4][8][16]}; $i += 4) {
    my $id = "$side.slot$tree->[1][4][8][16][$i][8][2]";
    $disks{$id} = 1;
    $disk_attrs{$id}{'disk-vendor-id'} = $tree->[1][4][8][16][$i][16][2];
    $disk_attrs{$id}{'disk-sector-capacity'} = $tree->[1][4][8][16][$i][32][2];
    if ( $debug ) {
      print "** DEBUG DATA  ** found $id\n";
      print "** DEBUG DATA  **   $id disk-vendor-id is $disk_attrs{$id}{'disk-vendor-id'}\n";
      print "** DEBUG DATA  **   $id disk-sector-capacity is $disk_attrs{$id}{'disk-sector-capacity'}\n";
    }
  }
}

#==============================================================================

sub write_disk {
  my ($which,$prefix) = @_;
  $loc = "${prefix}$which";
  $type = "Disk Drive";
  if ( $disks{$which} ) {
    $model = "$disk_attrs{$which}{'disk-vendor-id'}";
    $size = sprintf("%.2f GB",$disk_attrs{$which}{'disk-sector-capacity'}/2/1024/1024);
    if ( $rebuilding_disks{$which} ) {
      $status = "Rebuilding";
      if ( $exit_status < 1 ) {
        $exit_status = 1;
      }
    } else {
      $status = "Optimal";
    }
  } else {
    $model = '-- UNKNOWN --';
    $size = '0 GB';
    $status = 'Missing';
    if ( $exit_status < 2 ) {
      $exit_status = 2;
    }
  }
  write;
}

#==============================================================================

sub write_raid {
  my ($which) = $_[0];
  $loc = $which;
  $type = "RAID $raid_attrs{$which}{'raid-level'}";
  $model = "Apple Xserve RAID";
  $size = sprintf("%.2f GB",$raid_attrs{$which}{'sector-capacity'}/2/1024/1024);
  $status = $raid_attrs{$which}{'raid-status'};
  $status =~ s/online/Optimal/;
  CASE: { 
    if ( $raid_attrs{$which}{'initialize'} < 101 ) {
      $status = "$raid_attrs{$which}{'initialize'}% Built";
      if ( $exit_status < 1 ) {
        $exit_status = 1;
      }
      last;
    }
    if ( $raid_attrs{$which}{'add-member'} < 101 ) {
      $status = "$raid_attrs{$which}{'add-member'}% Rebuilt";
      if ( $exit_status < 1 ) {
        $exit_status = 1;
      }
      last;
    }
    $status =~ s/degraded/Degraded/;
    if ( $status eq 'offline' ) {
      $status = 'DEAD';
      $exit_status = 3;
    }
  }
  write;
}

#==============================================================================

sub read_conf_file {
  my $s = '';
  my $cf_file = "$etc_dir/$conf_file";
  if ( $debug ) { print "** DEBUG CONF ** $cf_file **\n"; }
  if ( ! open(IN,"<$cf_file") ) {
    print "xserve-raid-status: $etc_dir/$conf_file: $!\n";
    exit 1;
  }
  while (<IN>) {
    if ( $_ =~ /^\s*$/ ) { next; }
    chop; $_ =~ s/(.*?)\#.*/$1/;
    $s .= $_;
    while ( $_ !~ /;/ && !(eof(IN)) ) {
      $_ = <IN>;
      chop; $_ =~ s/(.*?)\#.*/$1/;
      $s .= $_;
    }
    if ( $s !~ /;/ ) {
      print "xserve-raid-status: $cf_file: syntax error: no semicolon for the last entry\n";
      exit 5;
    }
    $s =~ s/(.*?);.*/$1/;
    if ( $s =~ /(\S+)\s*=\s*(.*)/ ) {
      my $k = $1; my $v = $2;
      if ( $k =~ /_list$/ ) {
        $cf{$k} = [ split(/\s+/,$v) ];
        if ( $debug > 1 ) {
          print "  \$cf{\'$k\'} = wq( ";
          print join " ", @{ $cf{$k} };
          print " );\n";
        }
      } else {
        $cf{$k} = $v;
        if ( $debug > 1 ) { print "  \$cf{\'$k\'} = $v;\n"; }
      }
    } else {
      print "xserve-raid-status: $cf_file: $s: syntax error\n";
      exit 5;
    }
    $s = '';
  }
  close(IN);
}


#==============================================================================

sub usage {
print <<EOT
usage: xserve-raid-status [args] 
  --list mode        list logical, physical or both (default is both)
  --passwd passwd    encrypted passwd (default is $passwd aka "public")
  --address a.b.c.d  check status of raid at a.b.c.d (default is $addr)
  --debug            for debug output (use twice to get source xml output)
EOT
}
