????

Your IP : 216.73.216.174


Current Path : /lib/raider/Raider/
Upload File :
Current File : //lib/raider/Raider/Info.pm

use strict;
use warnings;

package Raider::Info;
use base qw( Raider::Base );

use Raider::Jobs;
use Raider::Devices;
use JSON::Tiny qw(decode_json encode_json);

use File::Find ();
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;
use Cwd (); # Included in perl

=head1 NAME

Raider::Info - Base class for Info related tasks

=head1 DESCRIPTION

Base class for all info objects. 

=head1 USAGE

use Raider::Info;
my $info_obj = Raider::Info->new();

=head1 METHODS

=head2 do_get_info()

Delegates the first steps in --get-info command line argument.

=head2 get_os()

Returns either redhat or debian. If neither, then error is thrown.

=head2 get_os_release()

Returns LSB OS release.

=head2 is_redhat()

Returns true if OS is Red Hat.

=head2 is_debian()

Returns true if OS is Debian (or fork Ubuntu).

=head2 write_json(\%args)

Write JSON to file.

=head2 get_pkg_mngr_cmd()

Returns what package manager command to use.

=head2 clean()

Clean .info file(s).

=head2 get_info_files()

Return present .info file(s).

=cut


sub do_get_info {
  my $self = shift;
  my $jobsObj = Raider::Jobs->new();
  my $devObj = Raider::Devices->new();
  my @jobs = @{ $jobsObj->get_job_list() };
  if ( scalar @jobs == 0 ) {
    $self->logger({ cat => 'i', msg => "do_get_info() called but no job files present, calling get_devices()..." });
    $devObj->get_devices();
    @jobs = @{ $jobsObj->get_job_list() };
  }
  $self->clean(); # removes all .info files

  my $d_struct_controllers = { controllers => { }, };
  my $d_struct_disks = { disks => { }, };
  my $d_struct;

  my ($d_struct_controllers_adaptec,$d_struct_disks_adaptec);
  my ($d_struct_controllers_3ware,$d_struct_disks_3ware);
  my ($d_struct_controllers_megaraidsas,$d_struct_disks_megaraidsas);
  my ($d_struct_controllers_FusionMPTSAS2,$d_struct_disks_FusionMPTSAS2);
  my ($d_struct_controllers_StorCli,$d_struct_disks_StorCli);
  my ($d_struct_usb_storage_satascsiata,$d_struct_controllers_satascsiata,$d_struct_disks_satascsiata);

  # Always start at zero.
  my $cntrl_cnt = 0;
  my $disk_cnt = 0;
  for my $info_task ( @jobs ) {
    if ( $info_task eq '3ware' ) {
      use Raider::Info::3ware;
      my $info3ware = Raider::Info::3ware->new();
      ($d_struct_controllers_3ware,$d_struct_disks_3ware,$cntrl_cnt,$disk_cnt) = $info3ware->get_info({ 
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt  
        });
    }
    elsif ( $info_task eq 'MegaraidSAS' ) {
      use Raider::Info::MegaraidSAS;
      my $infoMegaraidSAS = Raider::Info::MegaraidSAS->new();
      ($d_struct_controllers_megaraidsas,$d_struct_disks_megaraidsas,$cntrl_cnt,$disk_cnt) = $infoMegaraidSAS->get_info({ 
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt  
        });
    }
    elsif ( $info_task eq 'MegaraidSATA' ) {
      use Raider::Info::MegaraidSATA;
      my $infoMegaraidSATA = Raider::Info::MegaraidSATA->new();
      $infoMegaraidSATA->get_info({ 
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt
        });
    }
    elsif ( $info_task eq 'Adaptec' ) {
      use Raider::Info::Adaptec;
      my $infoAdaptec = Raider::Info::Adaptec->new();
      ($d_struct_controllers_adaptec,$d_struct_disks_adaptec,$cntrl_cnt,$disk_cnt) = $infoAdaptec->get_info({ 
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt
        });
    }
    elsif ( $info_task eq 'satascsiata' ) {
      use Raider::Info::satascsiata;
      my $infoSatascsiata = Raider::Info::satascsiata->new();
      ($d_struct_usb_storage_satascsiata,$d_struct_controllers_satascsiata,$d_struct_disks_satascsiata,$cntrl_cnt,$disk_cnt) = $infoSatascsiata->get_info({ 
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt
        });
    }
    elsif ( $info_task eq 'Mdraid' ) {
      next; # TODO "someday"? when MikeN wants to go over this... <-- update: October, 2019.. :(
    } 
    elsif ( $info_task eq 'FusionMPTSAS2' ) {
      use Raider::Info::FusionMPTSAS2;
      my $infoFusionMPTSAS2 = Raider::Info::FusionMPTSAS2->new();
      ($d_struct_controllers_FusionMPTSAS2,$d_struct_disks_FusionMPTSAS2,$cntrl_cnt,$disk_cnt) = $infoFusionMPTSAS2->get_info({
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt
        });
    }
    elsif ( $info_task eq 'StorCli' ) {
      # hey, so past self was a bit of a jerk here.
      use Raider::Info::StorCli;
      my $info = Raider::Info::StorCli->new();
      ($d_struct_controllers_StorCli,$d_struct_disks_StorCli,$cntrl_cnt,$disk_cnt) = $info->get_info({
          cntrl_cnt => $cntrl_cnt,
          disk_cnt => $disk_cnt
        });
    } 
    else {
      $self->logger({ cat => 'w', msg => "Unrecognized device file found ($info_task), ignoring..." });
    }
  };

  $d_struct_disks = {
    %{ $d_struct_disks_adaptec || { } },
    %{ $d_struct_disks_3ware || { } },
    %{ $d_struct_disks_megaraidsas || { } },
    %{ $d_struct_disks_FusionMPTSAS2 || { } },
    %{ $d_struct_disks_StorCli || { } },
    %{ $d_struct_disks_satascsiata || { } }
  };

  $d_struct_controllers = {
    %{ $d_struct_controllers_adaptec || { } },
    %{ $d_struct_controllers_3ware || { } },
    %{ $d_struct_controllers_megaraidsas || { } },
    %{ $d_struct_controllers_FusionMPTSAS2 || { } },
    %{ $d_struct_controllers_StorCli || { } },
    %{ $d_struct_controllers_satascsiata || { } }
  };

  my $d_struct_usb_storage = {
    %{ $d_struct_usb_storage_satascsiata || { } },  
  };

  $d_struct = { 
    controllers => { 
      %{ $d_struct_controllers },
      %{ $d_struct_usb_storage }    
    },
    physical_disks => { %{ $d_struct_disks } }
  };

  $self->write_json({data => $d_struct, device => 'sonar'});
}

sub get_os {
  my $self = shift;
  if ( $self->is_redhat ) {
    return 'redhat';
  }
  elsif ( $self->is_debian ) {
    return 'debian';
  }
  else {
    $self->logger({ cat => 'c', msg => "Unsupported operating system." });
  }
}

sub get_os_release {
  my $self = shift;
  my $release = `lsb_release -r`;
  $release =~ s/Release://g;
  $release =~ s/\n//g;
  $release = $self->strip_whitespace({ string => $release });
  return $release;
}

sub fetch_cent_ver {
  my $self = shift;
  if ( $self->is_centos() ) {
    my $os_rel = $self->get_os_release();
    if ( $os_rel =~ /(\d)\.[0-9]+/ ) {
      return $1;
    }
    else {
      $self->logger({ cat => 'c', msg => "fetch_cent_ver() Failed to fetch CentOS major version!" });
    }
  }
  else {
    $self->logger({ cat => 'w', msg => "fetch_cent_ver() called on non-CentOS platform!" });
  }
}

sub is_centos {
  my $self = shift;
  return 0 unless ( $self->is_redhat() );
  return 1 if ( -e '/etc/centos-release' );
  # The above centos-release file doesn't exist on CentOS 5 and earlier of course.
  # We have to support back to CentOS 4.. so check redhat-release for centos.
  if ( -e '/etc/redhat-release' ) {
    my $rhrel = do {
      local $/ = undef;
      open my $fh, "<", '/etc/redhat-release' or $self->logger({ cat => 'c', msg => "Unable to open [/etc/redhat-release]: $!" });
      <$fh>;
    };
    return 1 if ( $rhrel =~ /centos/i );
  }

  return 0;
}

sub is_fedora {
  return ( -e '/etc/fedora-release' ) ? 1 : 0;
}

sub is_redhat {
  my $ret = ( -e '/etc/redhat-release' ) ? 1 : 0;
  return $ret;
}

sub is_debian {
  my $ret = ( -e '/etc/debian_version' ) ? 1 : 0;
  return $ret;
}

sub write_json {
  my $self = shift;
  my $opts = shift;

  my $json_to_write = encode_json($opts->{data});

  my $info_filename;
  if ( $opts->{device} =~ /_device_map/i ) {
    $info_filename = "$Raider::Base::base_conf{'data_path'}/$opts->{device}.info";
  }
  else {
    $info_filename = "$Raider::Base::base_conf{'info_path'}/$opts->{device}.info";
  }
  open INFO_FILE, ">", "$info_filename" or $self->logger({ cat => 'c', msg => "Unable to save $info_filename; got system error: $!" });
  print INFO_FILE $json_to_write;
  close(INFO_FILE);
}

sub get_pkg_mngr_cmd {
  my $self = shift;
  my $os = $self->get_os();
  if ( $os eq 'debian' ) {
    ## Don't use aptitude as of 01/12/12. Whether or not aptitude was able to install the package, its exit
    ## code will always be 0. This brings about much fail in detecting package manager cmd failures. --ssullivan
    return 'apt-get -y';
  }
  elsif ( $os eq 'redhat' ) {
    ## Support legacy 'lpyum'...
    if ( -e '/usr/local/lp/configs/yum/yum.conf' ) {
      return 'yum -y -c /usr/local/lp/configs/yum/yum.conf';
    }
    else {
      return 'yum -y';
    }
  }
  $self->logger({cat => 'c', msg => "troubles detecting the package manager command os: [$os]"});
}

sub clean {
  my $self = shift;
  for my $info_file ( @{ $self->get_info_files() } ) {
    if ( $info_file =~ /raider.lock/ ) { next; }
    my $to_rm = "$Raider::Base::base_conf{'info_path'}/";
    $to_rm .= $info_file;
    unlink($to_rm);
  };
}

sub get_info_files {
  my $self = shift;
  my $info_files_ref = $self->sysFindFile({ search_path => "$Raider::Base::base_conf{'info_path'}" });
  return $info_files_ref;
}

sub get_smart_attribs {
  my $self = shift;
  my $opts = shift;

  if ( $opts->{device} !~ /^\/dev\// ) {
    $opts->{device} = "/dev/" . $opts->{device};
  }

  my $smart_attribs;
  my $smart_attrib_output;
  if ( $opts->{d_flag} ) {
    $smart_attrib_output = `smartctl -A $opts->{device} -T permissive -d $opts->{d_flag} -s on`;
  }
  elsif ( $opts->{bus} && $opts->{bus} eq 'usb' ) {
    $smart_attrib_output = `smartctl -A $opts->{device} -T permissive -d usbsunplus -s on`;
  }
  else {
    $smart_attrib_output = `smartctl -A $opts->{device} -T permissive -s on`;
  }
  my $id_key;
  for my $line ( split /^/, $smart_attrib_output ) {
    
    if ( $line =~ /(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)/i ) {
      my $id = $1;
      $id_key = $id;
      my $attribute_name = $2;
      my $flag = $3;
      my $value = $4;
      my $worst = $5;
      my $thresh = $6;
      my $type = $7;
      my $updated = $8;
      my $when_failed = $9;
      my $raw_value = $10;

      # Turns out, some drives (like the Crucial_CT480M500SSD1 with the MU03 firmware) 
      # show invalid values for some SMART attributes. Example:
      #
      # [root@host ~]# smartctl -A /dev/sda|grep 194
      # 194 Temperature_Celsius     0x0022   067   059   ---    Old_age Always       -       33 (0 41 255 246)
      # 194 Temperature_Celsius     <== Data Page      |  WARNING: PREVIOUS ATTRIBUTE HAS TWO
      # 194 Temperature_Celsius     <== Threshold Page |  INCONSISTENT IDENTITIES IN THE DATA
      # [root@host ~]# 
      #
      # To account for this, if a ID is defined more than once, set all values
      # of the attribute to "INVALID".
      #
      #   --ssullivan Feb 13th, 2014

      if ( defined($smart_attribs->{ $id }) ) {
        $self->logger({ cat => 'w', msg => "get_smart_attribs() Attribute ID [$id] was already defined! Marking INVALID!" });
        $smart_attribs->{ $id } = {};
        $flag = 'INVALID';
        $value = 'INVALID';
        $worst = 'INVALID';
        $thresh = 'INVALID';
        $type = 'INVALID';
        $updated = 'INVALID';
        $when_failed = 'INVALID';
        $raw_value = 'INVALID'; 
      }
      else {
        # Only do RAW_VALUE_EXTRA if there is a space.
        if ( $raw_value =~ /\s/ ) {
          if ( $raw_value =~ /(\S+)(.+)/i ) {
            $raw_value = $1;
            if ( defined($2) && ! defined($smart_attribs->{ $id }) ) {
              $smart_attribs->{ $id }->{ 'RAW_VALUE_EXTRA' } = $2;
            }
          }
        }
      }

      $smart_attribs->{ $id }->{ 'ATTRIBUTE_NAME' } = $attribute_name; 
      $smart_attribs->{ $id }->{ 'FLAG' } = $flag; 
      $smart_attribs->{ $id }->{ 'VALUE' } = $value;
      $smart_attribs->{ $id }->{ 'WORST' } = $worst;
      $smart_attribs->{ $id }->{ 'THRESH' } = $thresh;
      $smart_attribs->{ $id }->{ 'TYPE' } = $type;
      $smart_attribs->{ $id }->{ 'UPDATED' } = $updated;
      $smart_attribs->{ $id }->{ 'WHEN_FAILED' } = $when_failed;
      $smart_attribs->{ $id }->{ 'RAW_VALUE' } = $raw_value;
    }
  }

  # If this doesn't exist, there was an error. Perhaps more than one LSI controller exists, so our 
  # device port is incorrect for this controller.
  if ( ! $id_key ) {
    $self->logger({ cat => 'w', msg => "Info/get_smart_attribs() failed to get SMART attributes." });
    $smart_attribs = { error => 1 };
  }

  return $smart_attribs;
}

sub get_smart_info {
  my $self = shift;
  my $opts = shift;

  if ( $opts->{device} !~ /^\/dev\// ) {
    $opts->{device} = "/dev/" . $opts->{device};
  }

  my $smart_info_output;
  if ( $opts->{d_flag} ) {
    $smart_info_output = `smartctl -i $opts->{device} -T permissive -d $opts->{d_flag}`;  
  }
  else {
    $smart_info_output = `smartctl -i $opts->{device} -T permissive`;
  }

  return $smart_info_output;
}

sub get_block_devices {
  my $self = shift;
  my $opts = shift;

  my $sysfs_block = '/sys/block';
  my @block_devices = ();
  opendir my $dh, $sysfs_block || $self->logger({ cat => 'c', msg => "get_block_devices() failed to open [$sysfs_block]: $!" });
  while (defined(my $name = readdir $dh)) {
    @block_devices = grep {-d "$sysfs_block/$name" && ! /^\.{1,2}$/} readdir($dh);
  }
  closedir $dh;

  return \@block_devices;
}

sub get_udev_block_device_info {
  my $self = shift;
  my $opts = shift;

  my $id_serial = 0;
  my $id_serial_short = 0;
  my $model = 'unknown';
  my $disk_bus = 'unknown';
  my $id_vendor = 'unknown';

  unless ( $opts->{block_device_name} ) {
    $self->logger({ cat => 'w', msg => "get_udev_block_device_info() not passed block_device_name, bailing out.." });
    return 'unknown';
  }

  # Older distros had a /dev/.udev/db/ dir, use that if it exists. Otherwise
  # use udevadm.
  # Ubuntu 12.04 (and greater?) doesn't seem to have a /dev/.udev/db..
  
  my $file = 0;
  if ( -e "/dev/.udev/db/block:$opts->{block_device_name}" ) {
    $file = "/dev/.udev/db/block:$opts->{block_device_name}";
  }
  elsif ( -e "/dev/.udev/db/block\@$opts->{block_device_name}" ) {
    $file = "/dev/.udev/db/block\@$opts->{block_device_name}";
  }
  if ( $file ) {
    open my $fh, '<', $file || $self->logger({ cat => 'c', msg => "Unable to open $file [$!]" });
    while ( defined( my $line = <$fh> ) ) {

      if ( $line =~ /^E:ID_SERIAL=(.+)/i ) {
        $id_serial = $1;
      }

      if ( $line =~ /^E:ID_SERIAL_SHORT=(.+)/i ) {
        $id_serial_short = $1;
      }
  
      if ( $line =~ /^E:ID_MODEL=(.+)/i ) {
        $model = $1;
      }

      if ( $line =~ /^E:ID_BUS=(.+)/i ) {
        $disk_bus = $1;
      }

      if ( $line =~ /^E:ID_VENDOR=(.+)/i ) {
        $id_vendor = $1;
      }
    };
    close $fh;
  }
  else {
    # Old distros like CentOS 4 do not have udevadm. Prevent non fatal split
    # warning in this case.
    if ( $self->in_path({ cmds => [ 'udevadm' ], mode => 'nofatal' }) eq 'present' ) {
      for my $line ( split /^/, `udevadm info --query=property --path=/sys/block/$opts->{block_device_name}` ) {
      
        if ( $line =~ /^ID_SERIAL=(.+)/i ) {
          $id_serial = $1;
        }
      
        if ( $line =~ /^ID_SERIAL_SHORT=(.+)/i ) {
          $id_serial_short = $1;
        }

        if ( $line =~ /^ID_MODEL=(.+)/i ) {
          $model = $1;
        }

        if ( $line =~ /^ID_BUS=(.+)/i ) {
          $disk_bus = $1;
        }
      
        if ( $line =~ /^ID_VENDOR=(.+)/i ) {
          $id_vendor = $1;
        }
      }
    }
    else {
      $self->logger({ 
          cat => 'i', 
          msg => "Unable to fetch block device information from udev for [$opts->{block_device_name}]" 
        });
    }
  }

  my $udev_info = {};
  if ( $id_serial_short ) {
    $udev_info = { 
      model => $model,
      id_serial => $id_serial_short,
      disk_bus => $disk_bus,
      id_vendor => $id_vendor
    };
  }
  elsif ( $id_serial ) {
    $udev_info = {
      model => $model,
      id_serial => $id_serial,
      disk_bus => $disk_bus,
      id_vendor => $id_vendor
    };
  }
  else {
    $udev_info = {
      model => $model,
      id_serial => 'unknown',
      disk_bus => $disk_bus,
      id_vendor => $id_vendor
    };
  }

  return $udev_info;
}

sub convert_to_mb {
  my $self = shift;
  my $opts = shift;

  my $size_mb = 0;
  chomp($opts->{unit}) if ($opts->{unit});
  chomp($opts->{value}) if ($opts->{value});
  $opts->{unit} = $self->strip_whitespace({ string => $opts->{unit} }) if ($opts->{unit});
  $opts->{value} = $self->strip_whitespace({ string => $opts->{value} });
 
  if ( $opts->{value} =~ /MB/i ) {
    if ( $opts->{value} =~ /(\d+)/i ) {
      return $1;
    }
    else {
      $self->logger({ cat => 'c', msg => "convert_to_mb() got invalid value [$opts->{value}]" });
    }
  }

  if ( $opts->{unit} =~ /kb|kib/i ) {
    $size_mb = $opts->{value} / 1024;
  }
  elsif ( $opts->{unit} =~ /mb|mib/i ) {
    $size_mb = $opts->{value};
  }
  elsif ( $opts->{unit} =~ /gb|gib/i ) {
    $size_mb = $opts->{value} * 1024;  
  }
  elsif ( $opts->{unit} =~ /tb|tib/i ) {
    $size_mb = $opts->{value} * 1024 * 1024;
  }
  elsif ( $opts->{unit} =~ /block/i ) {
    $size_mb = $opts->{value} * 512 / 1024 / 1024;
  }
  elsif ( $opts->{unit} =~ /b|bytes/i ) {
    $size_mb = $opts->{value} / 1024 / 1024;
  }
  else {
    $self->logger({ cat => 'w', msg => "convert_to_mb() unsupported unit [$opts->{unit}]" });
  }

  return $size_mb;
}

sub map_file_exists {
  my $self = shift;
  my $opts = shift;

  unless ( $opts->{device} ) {
    $self->logger({ cat => 'c', msg => "map_file_exists() not given device!" });
  }

  if ( -e "$Raider::Base::base_conf{'data_path'}/$opts->{device}.info" ) {
    return 1;
  }
  else {
    return 0;
  }
}

sub find_ld_block_device {
  my $self = shift;
  my $opts = shift;

  # This currently only applies to megaraidSAS and Adaptec modules. This is
  #  not needed for 3ware, because on a 3ware LD's block device the SMART info 
  #  from smartctl -i actually tells us what LD# the block device belongs to.
  #   --ssullivan May 28th, 2013

  if ( ! defined($opts->{icmd}) || ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{lds_array}) || ! defined($opts->{ld_num}) || ! defined($opts->{device}) ) {
    $self->logger({ cat => 'c', msg => "find_ld_block_device() Missing required arguments!" });
  }

  my $block_device = 'unknown';

  # Get block device list.
  my $disc_block_devices = $self->get_block_devices();

  my @controller_ld_block_devices = ();
  # Find all LD's block devices that belong to controller.
  for my $each_block_device ( @{ $disc_block_devices } ) {

    # Get vendor from /sys/block now in case we need it later.
    my $sys_block_vendor;
    my $sys_block_vendor_file = "/sys/block/$each_block_device/device/vendor";
    if ( -e $sys_block_vendor_file ) {
      $sys_block_vendor = do {
        local $/ = undef;
        open my $fh, "<", $sys_block_vendor_file or $self->logger({ cat => 'c', msg => "Unable to open [$sys_block_vendor_file]: $!" });
        <$fh>;
      };
    }

    if ( $opts->{device} eq 'adaptec' ) {
      for my $line ( split /^/, `smartctl -i /dev/$each_block_device` ) {
        # Adaptec 7000 series controllers don't populate Adaptec as the
        # vendor. Instead, they populate this field with the model. The sample
        # model I saw was 'ASR7160', so i'm using ASR regex to attempt
        # (admittedly hackishly) to catch more Adaptec controllers.
        if ( $line =~ /(Device|Vendor)\s*:\s+(Adaptec|ASR\S+)/i ) {
          # We found an Adaptec LD block device; add it.
          push(@controller_ld_block_devices, $each_block_device);
          last;
        }
        elsif ( $sys_block_vendor && $sys_block_vendor =~ /^Adaptec|^ASR/i ) {
          push(@controller_ld_block_devices, $each_block_device);
          last;
        }
      }
    }
    elsif ( $opts->{device} eq 'megaraidsas' ) {
      for my $line ( split /^/, `smartctl -i /dev/$each_block_device` ) {
        if ( $line =~ /(Device|Vendor)\s*:\s+LSI|megaraid|AVAGO/i ) {
          # We found an LSI LD block device; add it.
          push(@controller_ld_block_devices, $each_block_device);
          last;
        }
        elsif ( $sys_block_vendor && $sys_block_vendor =~ /^LSI/i ) {
          push(@controller_ld_block_devices, $each_block_device);
          last;
        }
      }
    }
    else {
      $self->logger({ cat => 'c', msg => "find_ld_block_device() Don't know what to do with device [$opts->{device}] !" });
    }
  }

  # Filter out any LD's that do not belong to the given controller.
  my $filtered_ld_block_devices_ref = $self->filter_ld_block_devices_by_controller({ ld_block_devices => \@controller_ld_block_devices, controller => $opts->{controller}, controllers => $opts->{controllers} });
  my @filtered_ld_block_devices = @{ $filtered_ld_block_devices_ref };

  my $lds_present = @filtered_ld_block_devices;
  if ( $lds_present == 1 ) {
    # One LD with a block device, so no need for further examination.
    $block_device = "/dev/$filtered_ld_block_devices[0]";
    $self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}]; only LD present with a block device." });
  }
  else {
    # Two or more LD's with a block device found.
    my $ld_size_match = 0;
    my $numwrites_match = 0;
    my $pci_pri_match = 0;


    ####################
    ##  "Size match"  ##
    ####################

    # Get the size of $block_device , and each LD # on controller. If sizes are different, match based on size. If not, proceed to "numWrites".

    # Our + || - range to match.
    my $size_match_mb_range = '100';
    my %ld_sizes = ();
    my $match_comp = 0;

    if ( $opts->{device} eq 'adaptec' ) {
      for my $each_ld ( @{ $opts->{lds_array} } ) {
        for my $line ( split /^/, `$opts->{icmd} getconfig $opts->{controller} LD $each_ld` ) {
          if ( $line =~ /^\s+Size(\s+)?:\s+(\d+)\s+(\S+)/i ) {
            $ld_sizes{ $each_ld } = $self->convert_to_mb({ unit => $3, value => $2 });
            $match_comp = $ld_sizes{ $each_ld };
          }
        }
      }
    }
    elsif ( $opts->{device} eq 'megaraidsas' ) {
      for my $each_ld ( @{ $opts->{lds_array} } ) {
        for my $line ( split /^/, `$opts->{icmd} -LDInfo -L$each_ld -a$opts->{controller}` ) {
          if ( $line =~ /^Size(\s+)?:\s+(\S+)\s+(\S+)/i ) {
            $ld_sizes{ $each_ld } = $self->convert_to_mb({ unit => $3, value => $2 });
            $match_comp = $ld_sizes{ $each_ld };
          }
        }
      }  
    }

    # Get number of same values in hash.
    my $num_matches_values_ld_sizes = grep {$_==$match_comp} values %ld_sizes;
    # Get number of keys in hash.
    my $num_keys_ld_sizes = keys(%ld_sizes);
    # If same values in hash == number of keys in hash, all LD sizes are the same. Bail!
    if ( $num_keys_ld_sizes == $num_matches_values_ld_sizes ) {
      $self->logger({ cat => 'i', msg => "Two or more same size LD's detected. Moving on to numWrites algorithm..." });
    }
    else {
      # Two or more same size LD's. Match on size.

      for my $each_ld ( @{ $opts->{lds_array} } ) {
        last if ( $ld_size_match );

        # Define high and low range.
        my $high_end = $ld_sizes{ $each_ld } + $size_match_mb_range;
        my $low_end = $ld_sizes{ $each_ld } - $size_match_mb_range;

        for my $controller_ld_block_device ( @filtered_ld_block_devices ) {
          
          # Get size of block device as reported by OS.
          my $os_block_device_size_file = "/sys/block/$controller_ld_block_device/size";
          if ( ! -e $os_block_device_size_file ) {
            $self->logger({ cat => 'c', msg => "find_ld_block_device() device size file [$os_block_device_size_file] does not exist!" });
          }
          my $block_device_size_os_level = 0;
          my $block_device_size_os_level_MB = 0;
          local $/ = undef;
          open FILE, $os_block_device_size_file || $self->logger({ cat => 'c', msg => "Couldn't open file [$os_block_device_size_file]: $!"});
          $block_device_size_os_level = <FILE>;
          close FILE;
          # Get size in MB.
          $block_device_size_os_level_MB = $self->convert_to_mb({ unit => 'block', value => $block_device_size_os_level });

          # If OS reported block device is within range of vendor tool reported size..
          if ( grep {$_ == $ld_sizes{ $opts->{ld_num} }} $low_end..$high_end ) {
            # If OS reported block device size (in MB) is within the allowed range of the vendor tool reported size (in MB), it's a match.
            if ( grep {$_ == $block_device_size_os_level_MB} $low_end..$high_end ) {
              # Found our match, update block_device.
              $block_device = $controller_ld_block_device;
              $ld_size_match = 1;
              last;
            }
          }
        }
      }
    }

    if ( $ld_size_match ) {
      $self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via LD Size." });
      return $block_device;
    }

    ####################
    ##  "numWrites"   ##
    ####################  

    # Check /sys/block/$block_device/stat , field 5. If it matches numwrites for the LD in the vendor tool, match based on this.
    
    # Our + || - match range.
    my $numwrites_range = '10';
    my %lds_numwrites = ();
    
    if ( $opts->{device} eq 'adaptec' ) {
      for my $each_ld ( @{ $opts->{lds_array} } ) {
        my $is_ld_status = 0;
        my $is_ld_status_correct_ld = 0;
        for my $line ( split /^/, `$opts->{icmd} getlogs $opts->{controller} stats tabular` ) {
          if ( $line =~ /^\s+logicaldrivestats/i ) {
            $is_ld_status = 1;
          }
          elsif ( $line =~ /^\s+interCmdTime/i ) {
            $is_ld_status = 0;
            $is_ld_status_correct_ld = 0;
          }

          if ( $is_ld_status ) {
            if ( $line =~ /^\s+id\s+\S+\s+(\d+)/i ) {
              my $disc_ld_writes = $1;
              if ( $disc_ld_writes == $each_ld ) {
                $is_ld_status_correct_ld = 1;
              }
            }
          }

          if ( $is_ld_status_correct_ld ) {
            if ( $line =~ /^\s+numWrites\s+\S+\s+(\d+)/i ) {
              $lds_numwrites{ $each_ld } = $1;
            }
          }
        }
      }
    }
    elsif ( $opts->{device} eq 'megaraidsas' ) {
      # Unfortunately, MegaCLI doesn't have any machanism for showing numwrites on a LD.  
    }

    # Determine which controller block_device is within our numWrites range.
    for my $each_ld ( @{ $opts->{lds_array} } ) {
      last if ( $opts->{device} eq 'megaraidsas' || $numwrites_match );
      # Define high and low range.
      my $high_end = $lds_numwrites{ $each_ld } + $numwrites_range;
      my $low_end = $lds_numwrites{ $each_ld } - $numwrites_range;

      for my $controller_ld_block_device ( @filtered_ld_block_devices ) {

        # Get numwrites as reported by OS.
        my $os_block_device_numwrites_file = "/sys/block/$controller_ld_block_device/stat";
        my $os_block_device_numwrites_raw;
        my $os_block_device_numwrites = 0;
        if ( ! -e $os_block_device_numwrites_file ) {
          $self->logger({ cat => 'c', msg => "find_ld_block_device() stat file [$os_block_device_numwrites_file] doesn't exist !" });
        }
        local $/ = undef;
        open FILE, $os_block_device_numwrites_file || $self->logger({ cat => 'c', msg => "Couldn't open file [$os_block_device_numwrites_file]: $!"});
        $os_block_device_numwrites_raw = <FILE>;
        close FILE;
        
        if ( $os_block_device_numwrites_raw =~ /^\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/ ) {
          $os_block_device_numwrites = $1;
        }

        # See if numwrites as reported by OS is within range of vendor tool reported numwrites.
        if ( $each_ld == $opts->{ld_num} ) {
          if ( grep {$_ == $os_block_device_numwrites} $low_end..$high_end ) {
            # Found our match, update block_device.
            $block_device = $controller_ld_block_device;
            $numwrites_match = 1;
            last;
          }
        }
      }
    }


    if ( $numwrites_match ) {
      $self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via numWrites." });
      return $block_device;
    }

    ####################
    ## PCI "Priority" ##
    ####################

    # Get the pci bus, channel, and location of all block devices that are of the same type as the controller. Aggregate those into an integer ("priority"), then
    # compare the LD index to the "priority". The one with the lowest numeric "priority" is the lowest numbered LD etc. 

    my %pci_priority_map = ();
    for my $each_ld_block_device ( @filtered_ld_block_devices ) {
      # Get and assign PCI "priority" of block device.
      $pci_priority_map{ $each_ld_block_device } = $self->get_pci_priority({ block_device => $each_ld_block_device });
    }

    # Sort pci priorities from lowest to highest.
    my @sorted_block_devices = ();
    foreach my $sorted_block_device ( sort {$pci_priority_map{$a} <=> $pci_priority_map{$b}} keys %pci_priority_map ) {
      push(@sorted_block_devices, $sorted_block_device);
    }

    if ( defined($sorted_block_devices[$opts->{ld_num}]) ) {
      $block_device = "/dev/$sorted_block_devices[$opts->{ld_num}]";
      $pci_pri_match = 1;
    }
    else {
      $self->logger({ cat => 'c', msg => "find_ld_block_device() Can't determine block device for ld_num [$opts->{ld_num}] !" });
    }

    if ( $pci_pri_match ) {
      $self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via PCI Priority." });
      return $block_device;
    }  

    if ( ! $ld_size_match && ! $numwrites_match && ! $pci_pri_match ) {
      $self->logger({ cat => 'c', msg => "Failed to find block device for LD [$opts->{ld_num}] with ld size match, numwrites, and PCI priority !" });
    }
  }

  return $block_device;
}

sub get_pci_priority {
  my $self = shift;
  my $opts = shift;

  if ( ! $opts->{block_device} ) {
    $self->logger({ cat => 'c', msg => "get_pci_priority() not given block_device!" });
  }

  my $pci_priority = 0;

  # Get all block devices.
  my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();

  for my $device_path ( keys %{ $all_bdev_ctrls } ) {
    if ( $all_bdev_ctrls->{ $device_path } =~ /$opts->{block_device}/i ) {

      my $pci_dev_str = $device_path;

      # Remove non-numbers.
      $pci_dev_str =~ s/[^\d+]//g;
      
      # Add all numbers up, to determine pci_priority.
      while ( $pci_dev_str =~ /(.)/g ) {
        $pci_priority = $pci_priority + $1;
      }

    }
  }


  return $pci_priority;
}

sub filter_ld_block_devices_by_controller {
  my $self = shift;
  my $opts = shift;

  if ( ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{ld_block_devices}) ) {
    $self->logger({ cat => 'c', msg => "filter_ld_block_devices_by_controller() missing required args!" });
  }

  my $filtered_ld_block_devices = 0;
  my $lds_by_controller = {};

  # Get all block devices.
  my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();

  for my $each_block_device ( @{ $opts->{ld_block_devices} } ) {  
    
    # Find the entry that corresponds with each_block_device
    for my $device_path ( keys %{ $all_bdev_ctrls } ) {
      
      if ( $all_bdev_ctrls->{ $device_path } =~ /$each_block_device/i ) {
        
        # Example, of the below, extracting [0000:00:02.0/0000:02:00.0/host0/target0]
        # /sys/devices/pci0000:00/0000:00:02.0/0000:02:00.0/host0/target0:0:2/0:0:2:0/block
        # /sys/devices/pci0000:00/0000:00:02.2/0000:04:00.0/host0/target0:2:0/0:2:0:0/block
        # /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0/host1/target1:2:0/1:2:0:0/block
        # /sys/devices/pci0000:00/0000:00:02.2/0000:04:00.0/host0/target0:2:1/0:2:1:0/block
        # /sys/devices/pci0000:3a/0000:3a:00.0/0000:3b:00.0/host0/target0:2:0/0:2:0:0/block
        if ( $device_path =~ /\/sys\/devices\/pci\d+:\S+?\/(\S+\/host\d+\/target\d+):/i ) {
          my $pci_host_addr = $1;
          $pci_host_addr =~ s/\D//g;
           
          my $score = 0;
          # Add all numbers up
          while ( $pci_host_addr =~ /(.)/g ) {
            $score = $score + $1;
          }

          push(@{ $lds_by_controller->{ $score } }, $each_block_device);
        }
        else {
          $self->logger({ cat => 'c', msg => "device_path [$device_path] unknown format!" });
        }
      }
    }
  }

  # Some controllers, like Adaptecs, number controllers starting at 1, not 0,
  # like MegaraidSAS controllers do. Account for this by checking if the
  # lowest numbered controller is 1 or 0.
  my @sorted_controllers = sort { $a <=> $b } @{ $opts->{controllers} };
  my $accessor;
  if ( $sorted_controllers[0] == 0 ) {
    $accessor = $opts->{controller};
  }
  elsif ( $sorted_controllers[0] == 1 ) {
    $accessor = $opts->{controller} - 1;
    $self->logger({ 
        cat => 'i', 
        msg => "Lowest controller numbered 1, decremented passed controller to compensate." 
      });
  }
  else {
    $self->logger({ 
        cat => 'c', 
        msg => "Lowest numbered controller doesn't start with 0 or 1; I don't know how to handle this!" 
      });
  }

  # Now that our hashref is keyed up by PCI priority, we can select its block
  # devices by controller number.
  $filtered_ld_block_devices = $lds_by_controller->{ ( sort {$a <=> $b} keys %{ $lds_by_controller } )[$accessor] };

  if ( ! $filtered_ld_block_devices ) {
    $self->logger({ cat => 'c', msg => "filter_ld_block_devices_by_controller() Cannot find block devices for controller [$opts->{controller}] !" });
  }

  return $filtered_ld_block_devices;
}

sub isPvopsXenDom0 {
  my $self = shift;

  return 0 unless (-e '/sys/hypervisor/type');

  open FILE, "</sys/hypervisor/type" or $self->logger({cat => 'c', msg => "Error opening /sys/hypervisor/type: $!"});
  my $hypervisor_type = do { local $/; <FILE> };
  close(FILE) or $self->logger({cat => 'c', msg => "Error closing /sys/hypervisor/type: $!"});

  return 0 if ($hypervisor_type !~ /xen/);

  open FILE, "</sys/hypervisor/uuid" or $self->logger({cat => 'c', msg => "Error opening /sys/hypervisor/uuid: $!"});
  my $hyp_uuid = do { local $/; <FILE> };
  close(FILE) or $self->logger({cat => 'c', msg => "Error closing /sys/hypervisor/uuid: $!"});

  # uuid is always 00000000-0000-0000-0000-000000000000 if its a dom0
  return 0 if ($hyp_uuid !~ /00000000-0000-0000-0000-000000000000/);

  if (`modinfo xen_blkback 2>/dev/null` =~ /\/lib\/modules\//) {
    $self->logger({cat => 'i', msg => "Xen pvops based dom0 detected, xen_blkback is loaded"});
    return 1;
  }

  return 0;
}

sub get_sys_ctrls_with_bdevs {
  my $self = shift;
  my $opts = shift;

  # pvops Xen dom0s do not populate a block file in /sys/devices..
  if ($self->isPvopsXenDom0) {
    # fudging data for now, since only use case of this is legacy xen zone and we dont need
    # sonar.info to be strictly correct about this part on those.
    my $fudged_data;
    my $fudged_template_pci = '/sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:INCREMENT/block';
    my $disc_block_devices = $self->get_block_devices();
    my $device_cnt = 0;
    for my $block_device (@{$disc_block_devices}) {
      (my $my_pci_addr = $fudged_template_pci) =~ s/INCREMENT/$device_cnt/g;
      $fudged_data->{ $my_pci_addr } = $block_device;
      $device_cnt++;
    }
    return $fudged_data;
  }

  unless ( -d '/sys/devices' ) {
    $self->logger({ cat => 'c', msg => "/sys/devices/ does not exist, cannot proceed!" });
  }

  # Xen (on Cent5 at least) shows devices like this:
  #   /sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:0/block:sda
  # Where a non-Xen does this:
  #   /sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:0/block
  # Thus we get to look for both.
  
  our @all_bdev_ctrls = ();
  sub wanted_xen {
    /^block:.*\z/s
    && push(@all_bdev_ctrls, $name);
  }
  sub wanted {
    /^block\z/s
    && push(@all_bdev_ctrls, $name);
  }
  File::Find::find({
      wanted => \&wanted_xen
    },
    '/sys/devices/'
  );
  File::Find::find({
      wanted => \&wanted
    },
    '/sys/devices/'
  );


  my $data;
  for my $device_path ( @all_bdev_ctrls ) {
    if ( $device_path =~ /\/sys\/devices\/\S+\d+:\S+\/\d+:(\S+:\S+\.\d+)/ ) {

      # On older distros like CentOS4 it be like this:
      #   /sys/devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block -> ../../../../../../block/sda
      # On more modern ones it will be like this:
      #   root@host # ls -l /sys/devices/pci0000:00/0000:00:1f.2/host2/target2:0:0/2:0:0:0/block
      #   drwxr-xr-x 10 root root 0 Sep 10 13:30 sda
      #   root@host # 

      if ( -l $device_path ) {
        # [root@host ~]# readlink -f /sys/devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block
        # /sys/block/sda
        # [root@host ~]#

        my $readlinked = `readlink -f $device_path`;
        if ( $readlinked =~ /\/sys\/block\/(\S+)/i ) {
          $data->{ $device_path } = $1;
        }
        else {
          $self->logger({ cat => 'c', msg => "Device [$device_path] readlinks into unknown format!" });
        }
      }
      elsif ( -d $device_path ) {
        my @device_path_entries = glob("$device_path/*");
        if ( defined($device_path_entries[0]) ) {
          unless ( -e $device_path_entries[0] ) {
            $self->logger({ cat => 'c', msg => "Missing [$device_path_entries[0]]" });
          }
          my($dev_short) = $device_path_entries[0] =~ /.*\/(.*)/;
          my $device_tmp = "/dev/$dev_short";
          unless ( -e $device_tmp ) {
            $self->logger({ cat => 'c', msg => "Extracted device [$device_tmp] non-existent!" });
          }
          $data->{ $device_path } = $dev_short;
        }
        else {
          $self->logger({ cat => 'c', msg => "No entries for [$device_path]!" });
        }
      }
      else {
        $self->logger({ cat => 'c', msg => "Device [$device_path] is of unknown type!" });
      }
    }
    else {
      $self->logger({ cat => 'i', msg => "Skipping non-physical device [$device_path]" });
    }
  }


  return $data;
}

sub find_suitable_block_device {
  my $self = shift;
  my $opts = shift;

  if ( ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{candidate_block_devs}) ) { 
    $self->logger({ cat => 'c', msg => "find_suitable_block_device() Missing required args!" });
  }
  
  my $suitable_block_device;

  my @block_device_candidates = @{ $opts->{candidate_block_devs} };

  # Determine if any LDs are on different controllers.
  my %block_devices_by_pcihost = ();

  my $pcihost_id = 0;
  my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();

  for my $each_cand_block_device ( @block_device_candidates ) {

    if ( $each_cand_block_device =~ /\/dev\/(\S+)/ ) {
      $each_cand_block_device = $1;
    }

    for my $device_path ( keys %{ $all_bdev_ctrls } ) {

      if ( $all_bdev_ctrls->{ $device_path } =~ /$each_cand_block_device/i ) {
        # /sys/devices/pci0000:00/0000:00:1f.2/host2/target2:0:0/2:0:0:0/block
        # /sys/devices/pci0000:00/0000:00:02.0/0000:02:00.0/host0/target0:0:2/0:0:2:0/block
        if ( $device_path =~ /\/sys\/devices\/pci\d+:\d+\/(\S+\/host\d+\/target\d+):/i ) {
          my $pcihost = $1;
          $pcihost =~ s/\D//g;

          # Add pcihost numbers up.
          my @chars = $pcihost =~ /./sg;
          $pcihost_id = 0;
          for my $num ( @chars ) {
            $pcihost_id = $pcihost_id + $num;
          }

          $block_devices_by_pcihost{ $pcihost_id } = $each_cand_block_device;
        }
      }
    }
  }

  # If there is more than one key, there is more than one controller.
  my $key_count = keys %block_devices_by_pcihost;

  if ( $key_count == 1 ) {
    $suitable_block_device = $block_devices_by_pcihost{ $pcihost_id };
  }
  else {
    # The lowest numbered key is the lowest numbered controller.
  
    # Sort our controllers array from lowest to highest.
    my @low_to_high_cntrls = sort { $a <=> $b } @{ $opts->{controllers} };
    my $passed_controller_score = 0;
    for my $sorted_cntrl ( @low_to_high_cntrls ) {
      $passed_controller_score++;
      last if ( $sorted_cntrl == $opts->{controller} );
    }

    my $loop_pass_sorted_devs_pcihost = 1;
    for my $key ( sort { $a <=> $b } keys %block_devices_by_pcihost ) {
      if ( $loop_pass_sorted_devs_pcihost == $passed_controller_score ) {
        $suitable_block_device = $block_devices_by_pcihost{ $key };
      }
      $loop_pass_sorted_devs_pcihost++;
    }

    unless ( defined($suitable_block_device) ) {
      $self->logger({ 
        cat => 'c', 
        msg => "find_suitable_block_device() Unable to determine suitable block device for controller [$opts->{controller}]." 
      });
    }
  }

  
  return $suitable_block_device;  
}

sub get_device_id {
  my $self = shift;
  my $opts = shift;

  my @x = split (/\//, $opts->{device_path});

  if ( $x[4] =~ /^\d+:(\S+)/ ) {
    return $1;
  }
  else {
    $self->logger({ 
        cat => 'c', 
        msg => "Unable to determine device ID from device path [$opts->{device_path}]" 
      });
  }
}


1;