#!/usr/bin/perl
#
# scan system for a disk resources and print a table
# 
# ATTENTION ! 
#    This is linux specific tool. Should be substituted with 
#    correct version for Mac (osx) and solaris
#
#    RHEL 5u5
#
# assumption: 
#   /dev/mapper
#   /sbin/tune2fs
#   /sbin/fdisk -l
#   /sbin/parted
#   /sbin/dmsetup info --noheadings -c -o minor
#
# output ex:
#mnt  ae01731e-d17c-4e40-bba8-b84102c29813 va-store     8192 /dev/mapper/vg0-store       /vasm/store
#free ae01731e-d17c-4e40-bba8-b84102c29813 va-default  18192 /dev/mapper/vg0-default     
#free ae01731e-d17c-4e40-bba8-b84102c29813 va-0001     48192 /dev/sdd1
#
# Notes:
#    1. Called by apl with sudo. Ex:
#            sudo $APL/sm/sbin/sm_scan
#    2. Ueses $APL/var/conf/sm/options for a label filter
#    3. The argument can be used for finding particular volume. label filter from options will be ignored. Ex:
#            sudo $APL/sm/sbin/sm_scan va-store
#    4. sudo $APL/sm/sbin/sm_scan '<none>' shows volumes without labels
#    5. sudo $APL/sm/sbin/sm_scan '<all>' shows all volumes (with and without labels)


use strict;
#use Data::Dumper;

#----------------------------------- external calls
my $MAPPER   ='/dev/mapper';
my $TUNE     ='/sbin/tune2fs -l';
my $XFSADM   ='/usr/sbin/xfs_admin';
my $BLKID    ='/sbin/blkid';
my $PARTED   ='/sbin/parted';
my $FDISKLIST='/sbin/fdisk -l 2>/dev/null | egrep "^\/dev\/"';
my $DF       ='/bin/df -P';
my $DMINFOMIN='/sbin/dmsetup info --noheadings -c -o minor';
#----------------------------------- constants
my $APL      ='/opt/sarch';                       # root cannot know APL
my $WHEELS   ="$APL/var/conf/sm/wheels";
my $OPTIONS  ="$APL/var/conf/sm/options";         # VALID_NAME=va-\d+
my $BYUUID   ="$APL/var/sm/disk-by-uuid";         # disk by uuid collection
#----------------------------------- args
my $LABEL    =$ARGV[0];

sub diskinfo {
 my ($dev,$used,$mnt)=@_;
 my %diskinfo;
 #print "\ndiskinfo $dev\n";
 eval{ 
    #------- find a alias for iostat info
    my $alias;
    if($dev=~m|/dev/mapper/|) { # costruct alias from dmsetup info
      my $minor=`$DMINFOMIN $dev`; chomp $minor;
      $alias="dm-$minor";
    }else{
      $alias=$1 if $dev=~/(\w+?)\d*$/;
    }
    #------- get filesystem info
    open(XFSADM, "$XFSADM -ul $dev 2>/dev/null|") || die "XFSADM ERROR";
    my %info=map{/^(.+?)\s*=\s*"?(\S*?)"?$/} grep {/^.+\s*=\s*.+/} <XFSADM>;
    close XFSADM;
    my $name=$info{label};
    my $id=$info{UUID};
    my $size=0;
    %diskinfo=(UUID=>$id,DEV=>$dev,USED=>$used,SIZE=>$size,ALIAS=>$alias,MNT=>$mnt,NAME=>$name);
 }; 
 #print %diskinfo;
 %diskinfo;
}

sub get_block_device_size {
    my $dsk = shift;
    my ($dev, $alias) = ($dsk->{DEV}, $dsk->{ALIAS});
    if ($dev =~ m|/dev/mapper|) {
        $dev = $alias;
    } else {
        $dev =~ /.+\/(\w+)$/;
        $dev = $1;
    }
    return `cat /proc/partitions |grep $dev | cut -c 14-24`;
}

# MAIN ===========================================================
my %disks;
if( -d $MAPPER) { #------------- we have a mapper, read it!
  #print "================ MAPPER =============================\n";
  opendir(DIR, $MAPPER ) || die "can't opendir $MAPPER: $!";
  my @files = grep { ! /^\.\.?$/ and ! /^control$/ } readdir(DIR);
  close DIR;
  foreach my $file (@files) {
   my %dev=diskinfo("$MAPPER/$file",'free','none');
   $disks{$dev{UUID}}={%dev} if %dev;
  }
}

#-------------------------------- get list from fdisk -l
#followinbg output is expected from fdisk -l:
#/dev/sdb1   *           1         467     3751146   83  Linux
#/dev/sdb2             468         495      224910    5  Extended
#print "============================== FDISK ==============================\n";
open(LIST, "$FDISKLIST 2>/dev/null|") || die "cannot do $FDISKLIST";
foreach(<LIST>) {
   next if not m!^(/dev/\S+)\s+\*?\s+\d+\s+\d+\s+\d+\+?\s+(83|ee)\s+!;
   my  %dev=diskinfo($1,'free','none');
   $disks{$dev{UUID}}={%dev} if %dev && not exists $disks{$dev{UUID}};
}
close LIST;

#-------------------------------- get list from parted
# followinbg output is expected from parted -l:
#Disk /dev/sda: 36.4GB
#Sector size (logical/physical): 512B/512B
#Partition Table: msdos
#
#Number  Start   End     Size    Type     File system  Flags
# 1      32.3kB  107MB   107MB   primary  ext2         boot 
# 2      107MB   36.4GB  36.3GB  primary               lvm  
#
#Model: MegaRAID LD 1 RAID0 34G (scsi)
#Disk /dev/sdb: 36.4GB
#Sector size (logical/physical): 512B/512B
#Partition Table: gpt
#
#Number  Start   End     Size    File system  Name    Flags
# 1      17.4kB  3000MB  3000MB  ext3         VA-123       
# 2      3000MB  3020MB  20.0MB               v2           
# 3      3020MB  3040MB  20.0MB               v3           
# 4      3040MB  3060MB  20.0MB               v4           
# 5      3060MB  5000MB  1940MB  ext3         va-234       
# 6      5000MB  20.0GB  15.0GB  ext3              
#print "============================== PARTED ======================\n";
if( -x $PARTED and open(PLIST,"$PARTED -s -l 2>/dev/null|")) {    # only if parted is available
 my @plist=<PLIST>;
 close PLIST;
 my $dev='';
 foreach(@plist) {
   $dev=$1 if m|^Disk\s(/.+):|;
   next if $dev=~m|^/dev/mapper|; # Mapper devices already processed above
   next if not /^\s*(\d+)\s.+\s(ext3|xfs)/;
   my $devname="$dev$1";                   # like /dev/sdb1, /dev/sdb2 etc
   $devname="${dev}p$1" if -e "${dev}p$1"; # like /dev/cciss/c0d1p1
   my %dev=diskinfo($devname,'free','none');
   $disks{$dev{UUID}}={%dev} if %dev && not exists $disks{$dev{UUID}};
 }
}
# -------------------------------      get device list from df
open(DF, "$DF 2>/dev/null|") || die ("Cannot get $DF");
my %dfinfo= map {m|(/dev/\S+)(?:\s+\d+){4}%\s+(\S+)|} grep {m|/dev/\S+\s|} <DF>;
close DF;
foreach my $file (keys %dfinfo) {
   my %dev=diskinfo("$file",'mnt',$dfinfo{$file});
   $disks{$dev{UUID}}={%dev} if %dev;
}

# ------------------------------- mark vasm devices
#print "============= SCAN ALL DETECTED DEVICES ===============\n";
foreach (keys %disks) {
 $disks{$_}->{USED}='vasm' if -f "$WHEELS/$_";
 #print "$_ => $disks{UUID}\n";
}
# -------------------------------      read mask if exists
my $valid_label='';
if ( -f $OPTIONS) {
  if(open OPTIONS,$OPTIONS) {
    my %options=map{/(^\w+)=(.+)/} grep {/^\w+=.+/} <OPTIONS>;
    close OPTIONS;
    $valid_label=$options{VALID_LABEL} if exists $options{VALID_LABEL};
  }
}
# -------------------------------      use LABEL from command line if provided
$valid_label=$LABEL if $LABEL;
$valid_label=''     if $LABEL eq '<all>'; # force all volumes
#print "USING label=$valid_label\n";
# -------------------------------      print a table
foreach (keys %disks) {
 my $dsk=$disks{$_};
 next if $valid_label and not $dsk->{NAME} =~ /$valid_label/;
 # AF: workaround for XFS, which will return "0" size here
 $dsk->{SIZE} = get_block_device_size($dsk) if !$dsk->{SIZE};
 printf "%-4s %36s %-5s %8d %s\t%s\n",
        $dsk->{USED},$_,$dsk->{NAME},$dsk->{SIZE},$dsk->{DEV},$dsk->{MNT};
}
# -------------------------------      create disk-by-uuid
# we only create a record if /dev/disk/by-uuid exists 
# but mapper gives the different result
# this efford dedicated to linux multipath devices

`rm -rf $BYUUID.tmp >/dev/null 2>&1`; # remove tmp directory 
mkdir("$BYUUID.tmp",0755);
foreach (keys %disks) {
 my $dsk=$disks{$_};
 next if $valid_label and not $dsk->{NAME} =~ /$valid_label/;
 if($dsk->{DEV}=~m|^/dev/mapper/|) {     # multipath served by mapper
    symlink($dsk->{DEV},"$BYUUID.tmp/$_");
 }
}
`rm -rf $BYUUID >/dev/null 2>&1`;  # remove old directory
rename "$BYUUID.tmp",$BYUUID;      # rename new directory

#print 'disks:'. Dumper(\%disks);
