#
# Copyright (c) 2001, 2008, Oracle. All rights reserved.  
#
#  $Id: sUtilities.pm 22-apr-2008.05:31:31 rajverma Exp $ 
#
#
# NAME  
#   sUtilities.pm
#
# DESC 
#   utility subroutines 
#
#
# FUNCTIONS
# run_system_command( @ )   - Call a system command and time it out if necessary
# get_file_type($)
# get_device_id($)
# get_os_identifier_for_os_path( $ )
# get_source_link_file($;$)
# get_os_storage_entity_path ( $ )
# get_mount_privilege($)
# get_server_identifier( $;$ );
#
# NOTES
#
#
# MODIFIED  (MM/DD/YY)
# rajverma   04/22/08 - Merge IPV6 changes to 10.2.0.4.2
# mnihalan   01/01/08 - Porting merges
# spanchum   07/27/07 - Backport spanchum_bug-6002107 from main
# spanchum   05/02/07 - use ping for getting IP to detect nfs timeouts
# rrawat     07/03/07 - Bug-6168169
# rrawat     07/03/07 - Bug-6168169
# rrawat     07/23/07 - Backport rrawat_bug-6168169 from main
# rrawat     02/06/07 - Bug-5601303
# rrawat     03/26/07 - Backport rrawat_bug-4944947 from main
# aptrived   03/21/07 - Backport sejain_bug-5197714 from main
# sejain     02/17/07 - bug 5197714 : changing ERROR to WARN for symbolic links
# spanchum   10/25/06 - merge stpl changes - bug#5484304
# ssreenat   09/05/05 - get_server_identifier,ping,ARP and mount privilege support for AIX
# rrawat     06/21/06 - Bug-4955208
# ajdsouza   07/11/05 - changed comment for does_file_exist
# ajdsouza   06/20/05 - check for unitialized vendor and other variables
# ajdsouza   05/20/05 - run_system_command to return for any error other than
#                         timeout in eval block
#                       executing command
# ajdsouza   05/12/05 - abort instrumentation if os command timedout
#                       fix bug with handling alarm signal in run_system_commsnd
#                       bug# 4358925
# ajdsouza   03/02/05 - fixed bug for get_source_link_file to check before
#                        appending file_seperator to the begining
# ajdsouza   02/10/05 - fixed file_seperator bug in get_source_link_file 
# ajdsouza   02/06/05 - qualify error messages to be loaded to rep with ERROR:
# ajdsouza   01/26/05 - check if the cached filesystem exists
# ajdsouza   12/03/04 - get IP address using sockets
#                       moved this file from branch /main/unix
# ajdsouza   09/29/04 - 
# ajdsouza   09/09/04 - 
# ajdsouza   08/18/04 - Cached filesystem to device id comparison
# ajdsouza   08/11/04 - 
# ajdsouza   08/06/04 - 
# ajdsouza   07/20/04 - use perl call for null device
# ajdsouza   07/14/04 - Removed debug print line 
# ajdsouza   06/25/04 - storage reporting sources 
# ajdsouza   05/18/04 - Storage reporting perl modules 
# ajdsouza   04/14/04 - 
# ajdsouza   04/14/04 - UNix based common functions 
# ajdsouza   04/09/04 - 
# ajdsouza   04/08/04 - storage perl modules 
# ajdsouza   04/16/02 - Changes for GIT requiements
# vswamida   04/05/02 - getlistswraid returns null for Solaris now; getalldiskslices 
#                        calls listlinuxdiskpartitions.
# ajdsouza  04/04/02 - require v5.6.1
# ajdsouza  04/02/02 - Added the printList, print9IEMList, printEMDList functions
# vswamida  04/02/02 - getalldiskslices now returns null for Linux
# ajdsouza  04/02/02 - Uncommented use stormon_app
# vswamida  03/22/02 - Added stormon_linux
# ajdsouza  10/01/01 - Created
#
#

package storage::sUtilities;

require v5.6.1;

use strict;
use warnings;
use locale;
use File::Basename;
use File::Spec::Functions;
use storage::Utilities;
use Data::Dumper;

BEGIN
{

 use POSIX qw(locale_h);

 my $clocale='C';

 for ( qw ( LC_ALL LC_COLLATE LC_CTYPE LC_TIME LC_NUMERIC LC_MESSAGES LC_MONETARY LANG LANGUAGE ) )
 {
   $ENV{$_}=$clocale;
 }

 setlocale(LC_ALL,$clocale) or warn " Failed to set locale to $clocale \n ";

}

#-----------------------------------------------------------------------------------------
# Global package variable to hold sub name
our $AUTOLOAD;

#-------------------------------------------------------------------
# Variables with package scope
#-------------------------------------------------------------------
#------------------------------------------------------------------------------------
# Static Configuration
#------------------------------------------------------------------------------------

#Ping6 command pattern by OS
$storage::Register::config{ping6}{command}{solaris} = 'ping6 -n -s -t<TTL> <TARGET> 64 <NUMPACKETS>';
$storage::Register::config{ping6}{command}{linux} = 'ping6 -n <TARGET> -s64 -c<NUMPACKETS> -t<TTL>';
$storage::Register::config{ping6}{command}{hpux} = 'ping6 -n -t<TTL> <TARGET> 64 <NUMPACKETS>';
$storage::Register::config{ping6}{command}{aix} = '/etc/ping6 -s 64 -c <NUMPACKETS> -T <TTL> <TARGET>';
$storage::Register::config{ping6}{command}{dec_osf} = '/sbin/ping6 -n -s 64 -c <NUMPACKETS> -T <TTL> <TARGET>';
$storage::Register::config{ping6}{command}{windows} = 'ping6 -n -l64 -i<TTL> -n<NUMPACKETS> <TARGET>';
$storage::Register::config{ping6}{num_of_packets}=2;
$storage::Register::config{ping6}{time_to_live}=20;

$storage::Register::config{ping6}{results_pattern}{solaris} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{linux} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{hpux} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{windows} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{aix} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{dec_osf} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';

$storage::Register::config{ping6}{results_pattern_regex}{solaris} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern_regex}{linux} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern_regex}{hpux} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern_regex}{windows} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{aix} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';
$storage::Register::config{ping6}{results_pattern}{dec_osf} = '((?:[0-9a-fA-F]{0,4}:){5}[0-9a-fA-F]{1,4})';


#Ping command pattern by OS
$storage::Register::config{ping}{command}{solaris} = 'ping -s -t<TTL> <TARGET> 64 <NUMPACKETS>';
$storage::Register::config{ping}{command}{linux} = 'ping <TARGET> -s64 -c<NUMPACKETS> -t<TTL>';
$storage::Register::config{ping}{command}{hpux} = 'ping -t<TTL> <TARGET> 64 <NUMPACKETS>';
$storage::Register::config{ping}{command}{aix} = '/etc/ping -s 64 -c <NUMPACKETS> -T <TTL> <TARGET>';
$storage::Register::config{ping}{command}{dec_osf} = '/sbin/ping -s 64 -c <NUMPACKETS> -T <TTL> <TARGET>';
$storage::Register::config{ping}{command}{windows} = 'ping -l64 -i<TTL> -n<NUMPACKETS> <TARGET>';
$storage::Register::config{ping}{num_of_packets}=2;
$storage::Register::config{ping}{time_to_live}=20;

$storage::Register::config{ping}{results_pattern}{solaris} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';
$storage::Register::config{ping}{results_pattern}{linux} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';
$storage::Register::config{ping}{results_pattern}{hpux} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';
$storage::Register::config{ping}{results_pattern}{windows} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';
$storage::Register::config{ping}{results_pattern}{aix} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';
$storage::Register::config{ping}{results_pattern}{dec_osf} = '[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}';

$storage::Register::config{ping}{results_pattern_regex}{solaris} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';
$storage::Register::config{ping}{results_pattern_regex}{linux} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';
$storage::Register::config{ping}{results_pattern_regex}{hpux} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';
$storage::Register::config{ping}{results_pattern_regex}{windows} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';
$storage::Register::config{ping}{results_pattern}{aix} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';
$storage::Register::config{ping}{results_pattern}{dec_osf} = '([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})';


#Arp command pattern by OS
$storage::Register::config{arp}{command}{solaris} = 'arp <TARGET>';
$storage::Register::config{arp}{command}{linux} = 'arp <TARGET>';
$storage::Register::config{arp}{command}{hpux} = 'arp <TARGET>';
$storage::Register::config{arp}{command}{aix}  = 'arp <TARGET>';
$storage::Register::config{arp}{command}{windows} = 'arp <TARGET>';
$storage::Register::config{arp}{command}{dec_osf}  = 'arp <TARGET>';

$storage::Register::config{arp}{results_pattern}{solaris} = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';
$storage::Register::config{arp}{results_pattern}{linux} = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';
$storage::Register::config{arp}{results_pattern}{hpux} = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';
$storage::Register::config{arp}{results_pattern}{aix}  = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';
$storage::Register::config{arp}{results_pattern}{dec_osf}  = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';
$storage::Register::config{arp}{results_pattern}{windows} = '.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:';

$storage::Register::config{arp}{results_pattern_regex}{solaris} = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
$storage::Register::config{arp}{results_pattern_regex}{linux} = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
$storage::Register::config{arp}{results_pattern_regex}{hpux} = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
$storage::Register::config{arp}{results_pattern_regex}{aix}  = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
$storage::Register::config{arp}{results_pattern_regex}{dec_osf}  = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
$storage::Register::config{arp}{results_pattern_regex}{windows} = '(.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2}:.{1,2})';
#------------------------------------------------------------------------------------
# exports
#---------------------------------------------------------------------------------------
# No exports

#-----------------------------------------------------------------------------------------
# subs declared
#-----------------------------------------------------------------------------------------
sub run_system_command( @ );
sub get_device_id( $ );
sub get_os_identifier_for_os_path( $ );
sub get_source_link_file( $;$ );
sub get_file_type( $ );
sub get_os_storage_entity_path ( $ );
sub get_mount_privilege( $ );
sub get_server_identifier( $);
sub get_ipaddress( $;$ );

#------------------------------------------------------------------------------------------
# FUNCTION : run_system_command($;$$$)
#
# DESC 
# Run a system command , retry n times if it times out
#
# ARGUMENTS
# command to be executed
# timeout in seconds, default 30
# no of tries , default 2. 
# flag 1 to indicate if function should return in case of timeout
#  - default is to die
#----------------------------------------------------------------------------------------
# hash of list of exit status by command indicating failure , all other exit status for that
# command are construed to be success
my %exitstatuslist;
$exitstatuslist{df}=[];
$exitstatuslist{arp}=[];

sub run_system_command( @ )
{
    
  my ($cmd,$timeout,$tries,$freturn) = @_;
  my $devnull =  File::Spec->devnull();
  my $timedout = 0;
    
  $devnull = '/dev/null' 
   unless $devnull;
    
  warn "No command to execute!" 
   and return 
    if not $cmd;

  # Check if the hash for error command is initialized
  # if not initialize it

  
  if(!(keys %storage::Register::failed_command))
  {
     my $agent_state_target_dir = get_agentstatetarget_dir() or
     warn "ERROR:Failed to get directory to cache failed command on host \n"
      and return;

     my $cache_file = catfile($agent_state_target_dir,'nmhsfl.txt');

     # Read from the cached file and build the hash

     stat($cache_file);

    my @timeData = localtime(time);
    my $month = $timeData[4];
    my $year = $timeData[5];
    my $is_old_format = 0;

     if ( -e $cache_file and -r $cache_file )
    {

      if ( not open(CFH,'<',$cache_file) )
      {
        close(CFH) and
         warn "ERROR:Failed to open the cache file $cache_file for reading $!\n"
          and return;
      }
      else
      {

        for my $file_record (<CFH>)
        {
          chomp($file_record);
       
          # If old cycle format recycle it
          if($file_record =~ /Month::\d/i)
          {
            $is_old_format = 1;
          }

          if($file_record =~ /Month:/i)
          {
             my @date_entry = (-1,-1,-1);

             if( $is_old_format == 0)
             {
               @date_entry = split/:/,$file_record;
             }

             #Retry all the command on the first execution day of each month
             if($is_old_format == 1 or
                ($date_entry[2] < $timeData[5]) or
                ($date_entry[2] == $timeData[5] and
                  $date_entry[1] <= $timeData[4]))
             {
               #clear the hash for failed_command
               %storage::Register::failed_command = ();

               #increment the month entry by one month so that it is tried
               # only at next month
               if($month != 11)
               {
                 $month = $month + 1;
               }
               else
               {
                 $month = 0;
                 $year = $year + 1;
               }
               $storage::Register::failed_command{"Month:$month:$year"}=1;

               #Add an entry for the next month when it would be retried
               last;
             }
            
          } 
          $storage::Register::failed_command{$file_record} = 1;
        }

        close(CFH) or
         warn "Failed to close the cache file $cache_file after reading \n";

      }
    }
    else
    {
      #File does not exsists this is the first time.
   
    
      #increment the month entry by one month so that it is tried
      # only at next month
      if($month != 11)
      {
         $month = $month + 1;
      }
      else
      {
         $month = 0;
         $year = $year + 1;
      }
      #Add an entry for the month for which it would be rtried on each day when
      # the command would be retried
      $storage::Register::failed_command{"Month:$month:$year"} = 1;
      
    }

  }


  # Check if this command is in the list of failed commands and needs to be
  # skipped
  return if $storage::Register::failed_command{$cmd};

  # Disable timeout if nothing was passed as an argument
  # (setting alarm to 0 disables it)
  $timeout = 30
   if not $timeout;

  # Set the number of times to try running the command.
  $tries = 2 
   if not $tries;

  my ( $kid, $FH );
  my @value;
  my $diesh;

  # save the signal handler defined for die
  $diesh = $SIG{__DIE__}
   if $SIG{__DIE__};

  # catch the error here and die 
  # before enterign the eval block
  open(OLDERR,">&STDERR") 
   or die "ERROR:failed to open STDERR  during execution of command $cmd, storage metric instrumentation aborted\n";

  open(STDERR,"> $devnull")
   or die "ERROR:failed to redirect STDERR  during execution of command $cmd, storage metric instrumentation aborted\n";

  # remove any signal handler defined for die
  $SIG{__DIE__}='';

  for my $try (1..$tries)
  {

    $timedout=0;

    warn " Retrying $cmd , Count $try  \n" 
     if $try > 1;

    eval
    {

      # SIGNAL HANDLER FOR TIMEOUT
      # Kill process and set error if command times out
      local $SIG{ALRM} = sub
      {
        alarm 0;   #reset the alarm

        # kill the timedout process
        kill ("KILL", $kid) 
         if $kid;

        # set a flag to indicate that the command
        # timedout
        $timedout = 1;

        warn "$cmd timed out \n";

        # this die will end the eval block,
        # control will go to the line after the eval block
        die "Timed out on $cmd , $try times \n";

      };

      # return if error during forking of the command
      # discard stderr capture stdout only
      $kid = open $FH, "$cmd |";

      # error opening a fd to the command
      die "Failed to open descriptor to execute $cmd: $!\n"
       if not $kid;

      # Set the timeout
      alarm $timeout;

      @value = <$FH> 
       if $FH;

      #reset the alarm
      alarm 0;

      close $FH;

    };

    # Retry only for timeouts
    last 
     unless $timedout;

  }

  # restore back the original die signal handler
  $SIG{__DIE__} = $diesh
   if $diesh;

  # Restore STDERR
  close(STDERR);
  open(STDERR,">&OLDERR") 
   or die "ERROR:Failed to restore STDERR when executing OS command $cmd\n";
  close(OLDERR);

  # the command timed out after n tries
  if ( $timedout )
  {

    # if the caller specified the command to return, return with a error warning

    warn "ERROR:Timed out during execution of command $cmd after $tries tries\n"

     and return
      if $freturn;

    # do not try timedout commands again if they are not ignorable
    $storage::Register::failed_command{$cmd} = 1;
    # Abort instrumentation of storage metrics if the command timedout after $tries 
    #  and return is no specified
    die "ERROR:Timed out during execution of command $cmd after $tries tries, storage metric instrumentation aborted\n";
  }

  # if the eval block died for any other cause other than timeout
  # then $@ is set to error
  # indicating the die for the eval block
  # $@ is set ONLY if the eval block dies
  # return if any other error in while executing the command
  # not prudent to die here as absense of a command ( eq vxprint )
  # can cause this error, let the caller decide if it wants to die
  warn "ERROR:Failed during execution of command $cmd , error $@\n"
   and $storage::Register::failed_command{$cmd} = 1
   and return
    if $@;

  # If the OS command executed thru open and has set a error then
  # $? is set AFTER close, $! and $^E may or may not be set
  # log error and return
  # $? is 16 bytes , first 8 bytes gives the exit status
  # next 8 bytes indicate the mode of failure
  # Error only if exit status = 1
  # 0 or any other value construed to be a success value
  # df returns values other than 1 to indicate differnt failures
  # ignore these failures for df
  my $cmd0 = (split /\s+/,$cmd)[0];

  if (
      (
       exists $exitstatuslist{$cmd0} and 
       grep{1 if $?>>8 == $_ }@{$exitstatuslist{$cmd0}}
      )
      or
      (
       not exists $exitstatuslist{$cmd0} and $? >> 8 == 1 
      )
    )
  {
    # Our c executables set the error string in case of error
    if ( @value and $value[0] =~ /error::/i )
    {

      # We want to show the error to the user if the suid is not set
      # Hence dont skip those errors and dont mark them to be skipped
      if($value[0] =~ /root suid enabled/i )
      {
        #seperate error messages for the command line error and the
        #error logged from perl
        #ensures command line error is logged once
        warn "$value[0] \n";
        warn "Failed executing $cmd\n";
      }
      else
      {
        $storage::Register::failed_command{$cmd} = 1;
        #seperate error messages for the command line error and the
        #error logged from perl
        #ensures command line error is logged once
        warn "Failed executing $cmd  :: error:: $value[0] \n";
      }
    }
    else
    {
      $storage::Register::failed_command{$cmd} = 1;
      warn "Failed executing $cmd, $! $^E $? \n";
    }

    return;
  }

  # Return array or flat structure depending on type of return 
  # placeholder
  return wantarray ? @value : join("",@value);

}

#------------------------------------------------------------------------------------------
# FUNCTION : does_file_exist
#
# DESC 
# Return tru if the file exists 
#
# ARGUMENTS
# File or device name
#
#-----------------------------------------------------------------------------------------
sub does_file_exist( $ )
{
 
    my ( $fname ) = @_;

    warn "Filename is null \n" 
     and return 
      unless $fname;
    
    stat $fname;

    return unless -e $fname;

    return 1;
}

#------------------------------------------------------------------------------------------
# FUNCTION : get_os_identifier_for_os_path
#
# DESC 
# Get the mountpoint device id , filesystem inode to uniquely identify a file 
# stat returns the inode# of the source file for symbolic links
# 
#
# ARGUMENTS
# File or device name
#
#-----------------------------------------------------------------------------------------
sub get_os_identifier_for_os_path( $ )
{

    my ( $fname ) = @_;

    warn "Filename is null \n" 
     and return 
      unless $fname;
  
    warn "File $fname does not exist / inaccessible \n" 
     and return 
      unless does_file_exist($fname);
    
    my @stats = stat $fname;
    
    warn "stat failed for $fname \n" 
     and return 
      if not $stats[0] 
       or not $stats[1] ;
    
    return "$stats[0]-$stats[1]";
}


#------------------------------------------------------------------------------------------
# FUNCTION : get_source_link_file
#
# DESC 
# Move down all links and return the absolute path of the root link
#
# ARGUMENTS
# absolute Filename
# flag to ignore the lowest symbolic link template
#
#-----------------------------------------------------------------------------------------
sub get_source_link_file($;$)
{
  
  my ( $abs_file, $ignore_flag ) = @_;
  
  warn "Filename is null \n" 
   and return 
    unless $abs_file;
  
  warn "File $abs_file is inaccessible\n" 
   and return 
    unless -e $abs_file;
  
  my $file = $abs_file;

  while ( -l $file )
  {

    last if not $ignore_flag and 
     $storage::Register::config{lowest_symbolic_directory} and 
      $file =~ /$storage::Register::config{lowest_symbolic_directory}/i;
    
    my $linkfile = readlink $file;
    
    # Link file has absolute path
    my $absolute_path_start = get_absolute_path_start();
    $file = $linkfile and next 
     if $linkfile =~ /^$absolute_path_start/ ;
    
    # process the link file with an relative path
    #
    # Break up the link file and source file into dirs.
    my $file_seperator = get_file_seperator()
     or warn "ERROR:Unable to get the file seperator\n" 
      and return;
    
    my @linkdirs = split /$file_seperator/,$linkfile;
    my $filedir = dirname $file;
    my @filedirs = split /$file_seperator/,$filedir;
    
    # while link dirs are relative with a .. 
    # move down on the file dirs (remove last element) and 
    # move up ( remove first element) of link dirs
    while ( $linkdirs[0] =~ /^\.\./ )
    {
      shift @linkdirs;
      pop @filedirs;
    }

    # With no more relative paths join the two
    # paths to get the absolute path
    $file = join($file_seperator,@filedirs,@linkdirs);

    # if file seperator is similar to the absolute path begining
    # it should be appended as split on file_seperator will pluck it out
    $file = "$file_seperator$file"
     if $file
      and $file_seperator =~ /^$absolute_path_start/
       and $file !~ /^$absolute_path_start/;

    warn "ERROR:Link File $file for $abs_file is inaccessible\n" 
     and return $abs_file
      unless -e $file;

  }
  
  return $file;
  
}  


#------------------------------------------------------------------------------------
# FUNCTION : get_file_type
#
#
# DESC
# Return the file type for a special file as _CHARACTERSPECIAL or _BLOCKSPECIAL
# For regular files returns _REGULAR and directories as _DIRECTORY, others UNKNOWN
#
# ARGUMENTS:
# file or device name
#
#------------------------------------------------------------------------------------
sub get_file_type($)
{
  
  my $file_type;

  my ( $fname ) = @_;

  warn " Filename is null \n" and return unless $fname;
  
  warn " File $fname does not exist / inaccessible \n" and return unless does_file_exist($fname);
  
  my @stats = stat $fname;
  
  $file_type = 'FIFO_SOCKET_TTY' if -p _ or -S _  or -t _;
  
  # Block or Character
  $file_type .= '_CHARACTERSPECIAL' if -c _;
  $file_type .= '_BLOCKSPECIAL' if -b _;
  
  # Regular or special
  $file_type .= '_REGULAR' if -f _;
  $file_type .= '_DIRECTORY' if -d _;
  
  return $file_type;

}

#------------------------------------------------------------------------------------------
# FUNCTION : get_device_id
#
# DESC 
# Get the device id to uniquely identify a mountpoint 
#
# ARGUMENTS
# File or device name
#
#-----------------------------------------------------------------------------------------
sub get_device_id($)
{
  my ( $path ) = @_;

  warn " Filename is null \n" and return unless $path;
   
  warn " File $path does not exist / inaccessible \n" 
   and return unless -e $path;
    
  my @stats = stat $path;
    
  warn " stat failed for $path \n" and return 
   if not $stats[0];

  return "$stats[0]";

}

#------------------------------------------------------------------------------------
# FUNCTION : get_os_storage_entity_path
#
#
# DESC Returns the mountpoint for a regular file and the source file
# for a special file with symlink
#
# ARGUMENTS:
# File name
#
#------------------------------------------------------------------------------------
sub get_os_storage_entity_path ( $ )
{
    
  sub get_mountpoint_for_stat_device_id ( $ )
  {

    my ( $stat_device_id ) = @_;
    
    warn "Device ID is null" and return 
     unless $stat_device_id;

    return $storage::Register::mountpoint_id_index{$stat_device_id} 
     if keys %storage::Register::mountpoint_id_index
      and $storage::Register::mountpoint_id_index{$stat_device_id}
       and $stat_device_id eq get_device_id ($storage::Register::mountpoint_id_index{$stat_device_id});

    my $agent_state_target_dir = get_agentstatetarget_dir() or
     warn "ERROR:Failed to get directory to cache mountpoint metrics on host \n"
      and return;

    my $cache_file = catfile($agent_state_target_dir,'nmhsfcsh.txt');

    # Read from the cached file and build the hash
    stat($cache_file);

    if ( -e $cache_file and -r $cache_file )
    {

      if ( not open(CFH,'<',$cache_file) )
      {
        close(CFH) and 
         warn "Failed to open the cache file $cache_file for reading $!\n";
      }
      else
      {
     
        for my $file_record (<CFH>)
        {

          my @values = split/<<</,$file_record;
    
          next unless @values and @values == 2;
    
          map { s/^\s+|\s+$//} @values;
         
          next unless $values[0] and $values[1];
    
          $storage::Register::mountpoint_id_index{$values[0]}=
            $values[1]; 
    
        }
    
        close(CFH) or
         warn "Failed to close the cache file $cache_file after reading \n";

      }
   
    }

    # return the mounpoint from the cached file,
    # checking for the device id of the cached mountpoint will handle stale entries in cached file
    return $storage::Register::mountpoint_id_index{$stat_device_id} 
     if keys %storage::Register::mountpoint_id_index
      and $storage::Register::mountpoint_id_index{$stat_device_id}
       and $stat_device_id eq get_device_id ($storage::Register::mountpoint_id_index{$stat_device_id});

    # mountpoint not found in the cached file
    # instrument a new cached file to get the mountpoint
    %storage::Register::mountpoint_id_index = ();

    storage::Register::get_filesystem_metrics()
     or warn "ERROR:Failed to get the List of filesystems"
      and return;
    
    #----------------------------------------------------------------
    # for each mountpoint maintain a hash of device id to mountpoint
    #----------------------------------------------------------------
    for my $filesystem_ref( @storage::Register::filesystemarray )
    {
      
      next unless $filesystem_ref->{entity_type} =~ /mountpoint/i;            
      
      my $device_id = get_device_id($filesystem_ref->{mountpoint});
      
      $storage::Register::mountpoint_id_index{$device_id} =  $filesystem_ref->{mountpoint};
      
    }

    # Write to the cached file and buld the deviceid-fs hash
    stat($cache_file);
    
    if ( not open(CFH,'>',$cache_file)  )
    {
      close(CFH) 
       and warn "ERROR:Failed to open the cache file $cache_file for writing \n" 
        and return;
    }
    else
    {

      for my $device_id ( keys %storage::Register::mountpoint_id_index )
      {
        next unless $device_id and 
         $storage::Register::mountpoint_id_index{$device_id};
  
        print CFH "$device_id<<<$storage::Register::mountpoint_id_index{$device_id}\n" 
         or warn "Failed to write device id, mountpoint map to cache file $cache_file\n";
      }
      
      close(CFH) or
       warn "Failed to close the cache file $cache_file after writing \n";

    }

    return $storage::Register::mountpoint_id_index{$stat_device_id} 
     if keys %storage::Register::mountpoint_id_index and 
       $storage::Register::mountpoint_id_index{$stat_device_id};

    warn "Failed to return for device id $stat_device_id\n" 
     and return;

  }
  
  my ( $file_name ) = @_;
  
  my $source_file = get_source_link_file($file_name)
   or warn "WARN:Failed to check the symbolic links for $file_name"
    and return;
  
  my $file_type = get_file_type($source_file) or 
   warn "ERROR:Failed to get the file type for file $source_file $file_name" 
    and return;
  
  return $source_file if $file_type 
   and $file_type =~ /SPECIAL/i;
  
  my $mount_point_device_id = get_device_id($source_file) 
   or warn "Failed to get the device id for $source_file ,$file_name \n" 
    and return;
  
  return get_mountpoint_for_stat_device_id($mount_point_device_id) 
   or warn "ERROR:Mount point not found for $file_name";
  
}

#---------------------------------------------------------------------------------------
# FUNCTION :  get_mount_privilege
#
# DESC
# Return a hash array of the filsystem and mount privilege for that filesystem
#
# ARGUMENTS
# mountpoint
#--------------------------------------------------------------------------------------
sub get_mount_privilege($)
  {
    
    warn " Arg null in get_mount_privilege \n" and return unless $_[0];
    
    # Cache the results the first time
    if  ( not keys %storage::Register::mountprivilege )
    {
	 if ($^O eq "aix")
	 {
		for my  $myrecord ( storage::Register::run_system_command("mount",120) )	
		{
			chomp $myrecord;
			my $perm;
		    	my $node=1;
        		my $mount_point;
			$myrecord =~ s/^\s+|\s+$//g;
			next if $myrecord =~ /^(node|-----)/i;

			my @options = split /\s+/,$myrecord;
			
			if ( $options[0] =~ /^(\/|-)/)
			{
				$node = 0;
			}
			if ($node eq 0)
		    	{
                		$mount_point = $options[1];
                		$perm = $options[6];
        		}
	       		else
        		{
              			$mount_point = $options[2];
               			if (@options > 7)
               			{
                       			$perm = $options[7];
               			}
        		}	

            		warn " mount option $_ not well formatted, mountpoint is blank , skipping \n"
            		and next
            		unless $mount_point;

		    	my $mountprivilege;

        		$mountprivilege = 'READ';

        		$mountprivilege = 'WRITE'
			if $perm and $perm =~ /(write|rw)/i;

        		$mountprivilege = 'READ'
			if $perm and $perm =~ /ro/i;

			$storage::Register::mountprivilege{$mount_point} = $mountprivilege;
		}
	 }
         elsif ($^O eq "hpux")
         {

                my %mount_options;

                # Execute mount -v twice so it gives a complete
                # list of mounted filesystems
                my @dummy = storage::Register::run_system_command("mount -v",120);

                for ( storage::Register::run_system_command("mount -v",120) )
                {
                        chomp;

                        my @cols = split;

                        warn " mount option $_ not well formatted, unable to parse values , skipping \n"
                        and next
                        unless @cols > 2;
  			warn " mount option $_ not well formatted, mountpoint is  blank , skipping \n"
                                and next
                        unless $cols[2];

                        my $mount_point = $cols[2];

                        my @options = split /\s+/,$_;

                        warn " mount option $_ not well formatted, unable to read mount privilege\n"
                        and next
                        unless @options > 5;

                        $storage::Register::mountprivilege{$mount_point} = 'READ'
                        if $options[5] =~ /ro/i;

                        next if $storage::Register::mountprivilege{$mount_point}
                              and $storage::Register::mountprivilege{$mount_point} =~'READ';

                        $storage::Register::mountprivilege{$mount_point} = 'WRITE';

                }
         }
	 else
	 {

                my %mount_options;

		# Execute mount -v twice so it gives a complete 
  		# list of mounted filesystems 
  		my @dummy = storage::Register::run_system_command("mount -v",120);
  
  		for ( storage::Register::run_system_command("mount -v",120) )
    		{
      
      			chomp;
      
      			my @cols = split;
      
      			warn " mount option $_ not well formatted, unable to parse values , skipping \n" 
       			and next 
        		unless @cols > 2;
      
      			warn " mount option $_ not well formatted, mountpoint is blank , skipping \n" 
  				and next 
        		unless $cols[2];
      
    			my $mount_point = $cols[2];  
      
      			my @options = split /\s+/,$_;
      
      			warn " mount option $_ not well formatted, unable to read mount privilege\n" 
       			and next  
        		unless @options > 5;
      
      			$storage::Register::mountprivilege{$mount_point} = 'WRITE' 
       			if $options[5] =~ /write|rw/i;
      
      			next if $storage::Register::mountprivilege{$mount_point} 
       			and $storage::Register::mountprivilege{$mount_point} =~ 'WRITE';

      
      			$storage::Register::mountprivilege{$mount_point} = 'READ';
      
    		}
 	  } 
    }
    warn "ERROR:Mount Privilege not found for $_[0] \n" 
     and return unless $storage::Register::mountprivilege{$_[0]};
    
    return $storage::Register::mountprivilege{$_[0]};     
    
  }

#---------------------------------------------------------------------------------------
# FUNCTION :    get_ipaddress
#
# DESC   : Get the IP address of the server depending on the ping type (ping / ping6)
#
# ARGUMENTS
# Ping , Hostname
#---------------------------------------------------------------------------------------


sub get_ipaddress($;$)
 {

  my ($ping_type,$server_name) = @_ ;
  my $ip_address;

  #IPV6   :: Ping the host assuming its IPv4 host.
  #          If IP address is not returned use ping6 i.e its a IPv6 host.

 my $ping_command = $storage::Register::config{$ping_type}{command}{$^O};
    $ping_command =~ s/<TARGET>/$server_name/;
    $ping_command =~ s/<NUMPACKETS>/$storage::Register::config{$ping_type}{num_of_packets}/;
    $ping_command =~ s/<TTL>/$storage::Register::config{$ping_type}{time_to_live}/;

  for my $ping_results ( storage::Register::run_system_command($ping_command,10,1,1) )
    {
      chomp $ping_results;
      if (($^O eq "aix") or ($^O eq "hpux"))
      {
           next unless $ping_results =~ /$storage::Register::config{$ping_type}{results_pattern}{$^O}/;
      }
      else
      {
          next unless $ping_results =~ /$server_name/i and $ping_results =~ /$storage::Register::config{$ping_type}{results_pattern}{$^O}/;
      }
      next if $ping_results =~ /timeout|not\s*found|unreachable|unknown/i;

      if($ping_type eq "ping")
       {
                 ( $ip_address) = ( $ping_results =~ m/$storage::Register::config{$ping_type}{results_pattern_regex}{$^O}/ );
       }
      else
        {
                $ip_address = substr($ping_results, index($ping_results,"(")+1 , (index($ping_results ,")") - index($ping_results ,"(")) -1);
        }

      last if $ip_address;
    }


   return $ip_address ;

 }


#---------------------------------------------------------------------------------------
# FUNCTION :    get_server_identifier
#
# DESC
# Return the description of a given server.  Currently limited to Vendor name and if
# SNMP is active, Product type.
# NOTE: Nfs rpc calls do not provide enough distinguishing information to be a single
# reliable source for determining the vendor of a remote host.  Other checks such as the 
# ones used currently (telnet scan, http banner grab, arp, etc.) would have to be used in
# addition to the rpc calls.  The complexity of developing and maintaining an rpc call
# program did not provide enough added benefit to be worthwhile.
#
# ARGUMENTS
# Hostname
#---------------------------------------------------------------------------------------
# This sub should be deleted Once solaris is taken care of
sub get_server_identifier($)
{
  my ($server_name) = @_;
  my $output;
  my %nfs;
  my $mac_address;
  my $ip_address;
 
  my $address_type;
  my $ping_type = "ping";
 
  return unless $server_name;

  #IPV6   :: Ping the host assuming its IPv4 host.
  #          If IP address is not returned use ping6 i.e its a IPv6 host.

  # Ping before performing an arp
  # ping will populate the routing table with the mac address of the host if the host is in the same subnet

    $ip_address = get_ipaddress($ping_type, $server_name);

  #IPV6 ::  If we are not able to get $ip_address usig ping try ping6 i.e the host being pinged is IPV6 host.



  # Use ping is the IP address cannot be obtained thru perl socket calls
  if ( not $ip_address )
  {

    $ping_type = "ping6";
    $ip_address = get_ipaddress($ping_type, $server_name);

  }
 
 # Build the arp command ,to get the mac address of the nfs server, the nfs sever should be in the same subnet to get the mac address
  my $arp_command = $storage::Register::config{arp}{command}{$^O};
  $arp_command =~ s/<TARGET>/$server_name/;

  for my $arp_results (storage::Register::run_system_command($arp_command) ) 
  {
  
    chomp $arp_results;
  
    next unless $arp_results =~ /$server_name/i and $arp_results =~ /$storage::Register::config{arp}{results_pattern}{$^O}/;
  
    next if $arp_results =~ /(no\s+entry|not\s+found)/i;
 
    ( $mac_address ) = ( $arp_results =~ m/$storage::Register::config{arp}{results_pattern_regex}{$^O}/ );
  
    last if  $mac_address;
  
  } 
  
  # The mac adddress is saves if available , nfs server in the same subnet
  $nfs{nfs_server_net_interface_address} = $mac_address if $mac_address;
  
  # Save the IP address if available
  $nfs{nfs_server_ip_address} = $ip_address if $ip_address;
  
  return %nfs;

}

#-----------------------------------------------------------------------------------------
# FUNCTION : AUTOLOAD
#
# DESC 
# If sub is not defined here then look for it in sUtilities.pm
#
# ARGUMENTS
# Args to be passed to the sub
#
#-----------------------------------------------------------------------------------------
sub AUTOLOAD
{
  my ( @args ) = @_;
    
  my $sub = $AUTOLOAD;
    
  $sub =~ s/.*:://;	
    
  my $sub_path = "storage::Utilities::$sub";

  my $sub_ref = \&$sub_path;

  return &$sub_ref(@args);


}

sub trim
{
 
   #Arguments:  string or array
   #Outputs  :  string or array
   #Function : trim
 
   my (@out) = @_;
   for (@out)
   {
      s/^\s+//;
      s/\s+$//;
   }
 
   return wantarray ? @out : $out[0];
}

1; #Returning a true value at the end of the module
