#
# $Header: usm/src/cmds/acfslib/unix_linux/osds_unix_linux_acfslib.pm /st_usm_11.2/7 2011/07/22 13:35:26 averhuls Exp $
#
# osds_unix_linux_acfslib.pm
# 
# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. 
#
#
#    NAME
#      osds_unix_linux_acfslib.pm - Linux Unix OSD library components.
#
#    DESCRIPTION
#      Purpose
#          Linux Unix OSD library functions for the install/runtime scripts.
#
#    NOTES
#      All user visible output should be done in the common code.
#      this will ensure a consistent look and feel across all platforms.
#
#    MODIFIED   (MM/DD/YY)
#    averhuls    07/21/11 - XbranchMerge averhuls_crs_modify_and_more from main
#    averhuls    07/12/11 - Set USM_TRANSIENT_FAIL to 1.
#    averhuls    06/30/11 - XbranchMerge averhuls_acfsroot_return_codes from
#                           main
#    averhuls    06/17/11 - Add return codes.
#    plancian    05/03/11 - Export get_dev_mntpt_from_mount_line
#    averhuls    04/26/11 - XbranchMerge averhuls_persistent_log from main
#    averhuls    04/15/11 - Call acfsutil plogconfig -d to start OKS persistent
#                           logging after driver load.
#    anjiwaji    04/13/11 - Backport anjiwaji_bug-12347033 from main
#    anjiwaji    04/13/11 - Fix call to lib_osds_is_mounted.
#    gsanders    04/06/11 - Backport gsanders_bug_11839415-main from main
#    gsanders    04/05/11 - Move lib_osds_is_mounted to OSD libraries
#    anjiwaji    02/25/11 - Add extra debugging when verifying usm drivers.
#    gsanders    10/25/10 - add CLEAN_DO_NOT_KILL_LIST. Bug fixes. Code cleaning
#    gsanders    08/20/10 - add lib_osds_is_abs_path
#    averhuls    07/19/10 - Print PID info from clean when a PID is in use on a
#                           mountpoint. Fix lib_get_pid_info() /
#                           report_pid_info() hash list mismatch.
#    jinjche     07/13/10 - Fix bug 9879774 - fail to stop USM drivers resource
#    agraves     07/13/10 - Change fuser to use -a (report all pids) and lsof
#                           to use -b (don't block on system calls).
#    gsanders    07/07/10 - add lib_osds_create_mount_point
#    jinjche     06/28/10 - Fix a bug that ora.drivers.acfs resource always
#                           times out on stop in AIX
#    gsanders    06/24/10 - lib_osds_get_drive_info
#    jinjche     06/22/10 - Fix a bug that ora.registry.acfs resource does not
#                           start in AIX
#    averhuls    05/27/10 - Fix race between umount and fuser - bug 9746261.
#    averhuls    05/18/10 - Remove all text output from fuser leaving only
#                           the pid. bug9722397.
#    gsanders    10/04/30 - Merge forward 11.2.0.1 work
#    gsanders    05/05/10 - Move unsupported OS error msg print to osds libs
#    averhuls    04/28/10 - Use POSIX -c on the fuser command line rather than
#                           -m for compatibility.
#    jinjche     04/27/10 - Change my original fix to print an error if relative pathname
#                           for a mountpoint is provided to reflect review comments.
#    jinjche     04/21/10 - Fix bug that causes ACFS DBHOME resource fail to start
#                           with relative mountpoint pathname. Start and stop now work.
#    agraves     04/14/10 - Change message number so that acfsus.msg builds
#                           again.
#    agraves     04/12/10 - Massage message 1949 a bit.
#    agraves     04/09/10 - Convert 9999 messages to real NLS messages.
#    jinjche     04/01/10 - Fix bug 9536524
#    averhuls    03/26/10 - Handle different mount command output formats
#                           lin/sol/aix.
#    jinjche     03/25/10 - Allow the grid user to run acfsload on AIX
#    averhuls    03/22/10 - Do not chmod/chgrp after mounting a file system if
#                           the user has changed the mountpoint attributes.
#    averhuls    03/17/10 - Add USM_NOT_SUPPORTED.
#    averhuls    02/24/10 - Fix the Solaris ACFSUTIL location.
#    averhuls    11/17/09 - Solaris additions - different mount switches.
#    averhuls    11/05/09 - Export AVD_DIR.
#    agraves     10/01/09 - Cleanup name in header.
#    abakst      09/28/09 - Add Solaris support
#    abakst      09/03/09 - Add AIX support
#    averhuls    08/11/09 - Fix grep'ing when mountoint has trailing slash.
#                           Bug#8786154
#    agraves     07/28/09 - Remove osds_db_home function.
#    averhuls    06/30/09 - Report information when a PID could not be killed.
#    aime        01/22/10 - Add the use constant redirect here so that unix and
#                           linux variants still have error redirection.
#    anjiwaji    10/07/09 - Cleanup name in header
#    averhuls    07/01/09 - Report information when a PID could not be killed.
#    averhuls    06/10/09 - Handle NFS exported file systems.
#    averhuls    06/01/09 - Export TMPDIR.
#    agraves     05/18/09 - Creation of lib_osds_is_db_home for unix
#    agraves     05/12/09 - Fix for bug 8508861 - Both lsof and fuser should be
#                           used to check for existing processes on a mount
#                           point.
#    agraves     04/29/09 - Update messages to be consistent with acfsus.msg
#                           after Bill Manry's review.
#    averhuls    04/02/09 - Remove Linux specific "uname -i",
#                           use Perl $Config{archname}.
#    averhuls    02/16/09 - Remove debugging leftover.
#    averhuls    02/04/09 - rename usm_lib to acfslib.
#    averhuls    02/03/09 - Minor fix that generated a readline error.
#    agraves     01/28/09 - Add ACFSUTIL variable definition.
#    averhuls    01/14/09 - lib_osds_get_linux_type(): Fix rpmforge confusion -
#                           bug 7685472.
#    averhuls    01/13/09 - convert to new driver names - e.g., ofs.ko ->
#                           oracleoks.ko
#    averhuls    12/22/08 - convert to use message catalog.
#    averhuls    11/03/09 - Creation
#

use strict;
use File::Path;
use acfslib;
package osds_unix_linux_acfslib;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
                 lib_osds_am_root
                 lib_osds_control_devices_accessible
                 lib_osds_create_mount_point
                 lib_osds_device_from_mountpoint
                 lib_osds_get_advm_mounts
                 lib_osds_get_asm_user
                 lib_osds_get_drive_info
                 lib_osds_mount
                 lib_osds_mountpoint_descriptors
                 lib_osds_run_as_user
                 lib_osds_unmount
                 lib_osds_usm_supported
                 lib_osds_verify_usm_devices
                 lib_osds_is_db_home
                 lib_osds_is_abs_path
                 get_dev_mntpt_from_mount_line
                 @DRIVER_COMPONENTS
                 $ACFSUTIL
                 $TMPDIR
                 $REDIRECT
                 AVD_DIR
                 AVD_CTL_DEV
                 OFS_CTL_DEV
                 AVD_IDX
                 OFS_IDX
                 OKS_IDX
                 OPT_CHR
                 USM_FAIL
                 USM_SUCCESS
                 USM_NOT_SUPPORTED
                 USM_REBOOT_RECOMMENDED
                 USM_TRANSIENT_FAIL
                 );

use Config;
my ($OSNAME) = $Config{osname};
chomp($OSNAME);

# return/exit codes
#
# USM_TRANSIENT_FAILures are those that can be easily filed by the admin.
# In the case of "acfsroot install", the admin could fix the error and then
# resume a grid install, for example, from the checkpoint.
use constant USM_SUCCESS            => 0;
use constant USM_FAIL               => 1;
use constant USM_NOT_SUPPORTED      => 2;
use constant USM_REBOOT_RECOMMENDED => 3;
use constant USM_TRANSIENT_FAIL     => 1; # failures that can be easily fixed

use constant OPT_CHR => "-";            # Linux/Unix option character

use constant AVD_DIR     => "/dev/asm";         # created when AVD loaded
use constant AVD_CTL_DEV => "/dev/asm/.asm_ctl_spec"; # created when AVD loaded
use constant OFS_CTL_DEV => "/dev/ofsctl";      # created when OFS loaded

use constant AVD_IDX => 0;                      # index into driver_ccomponents
use constant OKS_IDX => 1;                      # index into driver_ccomponents
use constant OFS_IDX => 2;                      # index into driver_ccomponents

# On Windows, in the CRS environment, this 
# redirection does not work.  It fails with 
# "cannot open file descriptor" or "cannot open pipe NOWAIT".
our ($REDIRECT) = "2>&1";

my ($CACHED_INSTALLED_DRIVERS);               # saved find /lib/modules output
my ($CACHED_LOADED_DRIVERS);                  # saved find lsmod output

our ($ACFSUTIL) = "/sbin/acfsutil";           # Linux
$ACFSUTIL       = "/sbin/acfsutil" if ($OSNAME eq "aix");
$ACFSUTIL       = "/usr/lib/fs/acfs/acfsutil" if ($OSNAME eq "solaris");

our ($TMPDIR) = "/tmp";

# /sbin/fuser     ... RH Enterprise Linux
# /bin/fuser      ... Suse
# /usr/sbin/fuser ... AIX, HPUX, Solaris
# /usr/sbin/lsof  ... RH Enterprise Linux
# /sbin/modprobe  ... RH Enterprise Linux
$ENV{PATH} = $ENV{PATH} . ":/sbin:/bin:/usr/sbin";

# lib_osds_am_root
#
# verify root access
#
sub lib_osds_am_root
{
  if ($>) # get euid
  {
    # not zero (root)
    # If this is AIX, let the grid user in.
    if ($OSNAME eq "aix ")
    {
      my $orhome = $ENV{ORACLE_HOME};
      my $tusr=`/usr/bin/grep ORACLE_OWNER ${orhome}/crs/install/crsconfig_params|/usr/bin/cut -f2 -d '='`;
      my $me=`/bin/id -un`;
      $tusr =~ s/\s+$//;
      $me =~ s/\s+$//;
      if ($me eq $tusr)
      {
        return 1;
      } else
      {
        return 0;
      }
    }

    return 0;
  }

  return 1;
} # end lib_osds_am_root

# lib_osds_mount
#
# Mount the specified file system
#
sub lib_osds_mount
{
  my ($device, $mount_point, $options) = @_;
  my ($asmadmin);
  my ($result);
  my ($fs_switch);

  if ($OSNAME eq "linux")
  {
    $fs_switch = "-t";
  }
  elsif ($OSNAME eq "solaris")
  {
    $fs_switch = "-F";
  }
  elsif ($OSNAME eq "aix")
  {
    $fs_switch = "-v";
  }
  else
  {
    # should never get here if lib_osds_usm_supported() did its job.
    lib_error_print(9125,
                    "ADVM/ACFS is not supported on this OS: '%s'", $OSNAME);
    return USM_FAIL;
  }

  if ($options ne "none")
  {
    $options = "-o " . $options;
  }
  else
  {
    $options = "";
  }
  $result = system("mount $fs_switch acfs $options $device $mount_point");
  if ($result)
  {
    return USM_FAIL;
  }

  # set mount point attributes if the defaults have not been changed.
  my ($dev,$ino,$mode,$nlink,$uid,$gid) = stat($mount_point);
  if (($mode == 040755) && ($uid == 0) && ($gid == 0))
  {
    # The user has not changed the defaults.
    $asmadmin = acfslib::lib_get_asm_admin_name();
    system ("chmod 0770 $mount_point");
    system ("chgrp $asmadmin $mount_point");
  }

  # If the mount point is to be exported, do it here.
  # This will be a no-op if the mount point is not in /etc/exports
  acfslib::lib_osds_exportfs($mount_point);

  return USM_SUCCESS;
} # end lib_osds_mount

# lib_osds_mountpoint_descriptors
#
# called with action = 1 when the user calls "clean" for force any open file
# references to be cleared from a mount point to ensure that the unmount
# will succeed.
#
# called with action = 0 to print open references on the mount point.
#
sub lib_osds_mountpoint_descriptors
{
  my ($mountpoint, $action) = @_;
  my ($fuser) = "fuser";
  my ($have_fuser) = 1;
  my ($lsof) = "lsof";
  my ($have_lsof) = 1;
  my ($str);                       # temp work space
  my ($descriptor_list) = "";      # returned if action == 0       
  my ($pid) = "";
  my ($retval) = USM_SUCCESS;
  my ($busyCmd);                  
  my @CLEAN_DO_NOT_KILL_LIST = (
    '^/\S*/acfs_script.sh',
    '^asm_.*\+ASM\d$',
    '^\[asmError0]$',
    '^\[asmIntervalTime]$',
    '^\[asmShutdown]$',
    '^/\S*/crsd',
    '^/\S*/cssdmonitor',
    '^/\S*/diskmon',
    '^/\S*/evmlogger',
    '^/\S*/evmd',
    '^/\S*/gipcd',
    '^/\S*/gpnpd',
    '^/\S*/mdnsd',
    '^/\S*/ocssd',
    '^/\S*/octssd',
    '^/\S*/ohasd',
    '^/\S*/ologgerd',
    '^/\S*/ons',
    '^/\S*/oraagent',
    '^/\S*/orarootagent',
    '^/\S*/osysmond',
    '^/\S*/tnslsnr',
  );


  # All systems are "supposed" to have fuser (LSB_Core), but who really
  # knows.  Either fuser or lsof can do the job. Let's see what we have.
  open (WHICH, "which $fuser 2>&1 |");
  $str = <WHICH>;
  close (WHICH);
  if ($str =~ /no $fuser in/)
  {
    $have_fuser = 0;
  }

  open (WHICH, "which $lsof 2>&1 |");
  $str = <WHICH>;
  close (WHICH);
  if ($str =~ /no $lsof in/)
  {
    $have_lsof = 0;
  }

  # for debugging, uncomment the appropriate line below.
  # $have_fuser = 1; $have_lsof = 0;
  # $have_fuser = 0; $have_lsof = 1;

  if (($have_fuser == 0) && ($have_lsof == 0))
  {
    print "This system has neither fuser nor lsof in its command search path\n";
    if ($action)
    {
      return USM_FAIL;
    }
    else
    {
      return $descriptor_list;
    }
  }

  #
  #  Search for processes with open files on the target mount point
  #  using fuser.  If found, list or kill them.  fuser should be on
  #  most operating systems.
  #

  if ($have_fuser)
  {

    # If the mountpoint got unmounted before we run fuser, we could get all
    # pids on the system. To avoid this, we run lib_osds_is_mounted() after
    # fuser completes to ensure that the data is still valid. We used to call
    # lib_osds_is_mounted() and then fuser - which opened a race condition.
    #
    # If fuser gets an error, it will appear on a separate line so we need
    # to handle that. For example:
    #      Cannot stat file /proc/6318/fd/6: Stale NFS file handle
    #      /mnt1:               14134
    #
    # You won't believe this but on the line:
    #      /mnt1:               14134
    # "/mnt1: goes to STDERR and "14134" to STDOUT. So, we can't do 
    # something like "open (FUSER, "$fuser -c $mountpoint 2> /dev/null |");".

    if ($OSNAME eq "linux")
    {
      # Only linux has the -a flag to fuser.
      open (FUSER, "$fuser -ac $mountpoint 2>&1 |");
    }
    else
    {
      open (FUSER, "$fuser -c $mountpoint 2>&1 |");
    }

    my ($fuser_line);
    while ($fuser_line = <FUSER>)
    {
      my (@fuser_array) = split(/\s+/, $fuser_line);

      last if (! acfslib::lib_osds_is_mounted($mountpoint) );

      EACH_FUSER_PID: foreach $pid (@fuser_array)
      {
        if ($pid =~ /$mountpoint/)
        {
          # The list begins with "<mountpoint>:". Ignore that.
          next;
        }

        my ($first_char) = substr($pid, 0, 1);
        if ($first_char =~ /\D/)
        {
          # non-digit first character
          # e.g., Cannot stat file /proc/6318/fd/6: Stale NFS file handle
          next;
        }

        # strip non digit characters from (base 10) pid.
        #
        # fuser output: <mountpoint>: [<pid>[n]>
        #    where [n] is one or more of the following:
        # 'c' current directory symbol
        # 'e' executable running symbol
        # 'f' open file symbol
        # 'F' open for write symbol
        # 'm' mmap'ed file symbol
        # 'n' holding non-blocking lock (Solaris)  
        # 'o' open file (Solaris)  
        # 'r' root directory symbol
        # 's' shared library file (AIX)
        # 't' text file (Solaris)
        # 'y' controlling terminal (Solaris)
        $pid =~ s/[[:alpha:]]+//g;

        # Does the PID contain non-digits?
        if ($pid =~ /\D/)
        {
          # This is not a PID. Likely an error from fuser.
          next;
        }

        #
        #  This process has a file open on the mount point.  Get it's
        #  name and log it.
        #

        $busyCmd = qx(ps -p $pid -o args=);
        next EACH_FUSER_PID if ( ! $busyCmd );
        chomp $busyCmd;
        acfslib::lib_inform_print(9153,
          "Program '%s' with OS process ID '%s' is using mount point '%s'.",
          $busyCmd, $pid, $mountpoint );

        #
        #  Perform the requested action: list or kill.
        #

        if ($action == 1)    # clean
        {
          #
          # If process is our "safe list" don't kill it.
          #
          foreach my $safe ( @CLEAN_DO_NOT_KILL_LIST )
          {
            if ( $busyCmd =~ /$safe/ )
            {
              # This isn't going to end well ...
              acfslib::lib_inform_print(9152,
                "Program '%s' with OS process ID '%s' will not be terminated.",
                $busyCmd, $pid );
              $retval = USM_FAIL;
              next EACH_FUSER_PID;
            }
          }

          acfslib::lib_inform_print(9126,
            "Attempting to terminate the program '%s' with OS process ID '%s'.",
            $busyCmd, $pid);
          my $killRet = system("kill -9 $pid");
          if ( $killRet )
          {
            # The kill command failed. Either the process is already gone
            # or we can't kill it.
            my (%info) = lib_get_pid_info($pid);
            if (defined($info{'PID'}))
            {
              # The process is still alive.
              $retval = USM_FAIL;
              acfslib::lib_inform_print(9136,
                                       "PID %s could not be killed.", $pid);
              report_pid_info(%info);
            }
          }
        }
        else
        {
          $descriptor_list .= "$pid ";
        }
      }
    }
    close (FUSER);
  } # end if have_fuser

  #
  #  Search for processes with open files on the target mount point
  #  using lsof.  If found, list or kill them.  lsof isn't found on some
  #  operating system installations.
  #

  if ($have_lsof)
  {

    my $skipToNextProcess = 0;

    #
    # lsof may block. We may want to use the -b option to
    # avoid kernel calls that might block which can cause hangs
    # for several minutes. 
    #
    # Use '-F pn' option to get parsable output (pid, file name)
    #

    open (LSOF, "$lsof -b -F pn 2>/dev/null |");
    LSOF_LINE: while($str = <LSOF>)
    {

      # If pid line, save it and continue.
      if ( $str =~ /^p\d+$/ )
      {
        $pid = $str;
        $pid =~ s/^p//;
        chomp $pid;
        $skipToNextProcess = 0;
        next LSOF_LINE;
      }

      #
      # We should be looking at a file name line. ( "n/abc/def/ghi" );
      #
      # If we've already performed the desired action against the
      # process, skip to the next process.
      #

      next LSOF_LINE if ( $skipToNextProcess == 1 );

      # Next if this file is not on the target mount point.
      next LSOF_LINE if ( $str !~ /^n$mountpoint/ );

      #
      # This process has a file open on the mount point.  Get it's name
      # and log it.
      #

      $busyCmd = qx(ps -p $pid -o args=);
      if ( ! $busyCmd )
      {
        # Maybe it exited between the time lsof found it and now.
        $skipToNextProcess = 1;
        next  LSOF_LINE;
      }
      chomp $busyCmd;
      acfslib::lib_inform_print(9153,
        "Program '%s' with OS process ID '%s' is using mount point '%s'.",
        $busyCmd, $pid, $mountpoint );

      #
      #  Perform the requested action: list or kill.
      #

      if ($action == 1)    # clean
      {
        #
        # Don't kill this process if it's on our "safe list". 
        #
        foreach my $safe ( @CLEAN_DO_NOT_KILL_LIST )
        {
          if ( $busyCmd =~ /$safe/ )
          {
            # This isn't going to end well ...
            acfslib::lib_inform_print(9152,
              "Program '%s' with OS process ID '%s' will not be terminated.",
              $busyCmd, $pid );
            $skipToNextProcess = 1;
            $retval = USM_FAIL;
            next LSOF_LINE;
          }
        }

        acfslib::lib_inform_print(9126,
           "Attempting to terminate the program '%s' " .
           "with OS process ID '%s'.", $busyCmd, $pid);
        my $killRet = system("kill -9 $pid");
        if ( $killRet )
        {
          # The kill command failed. Either the process is already gone
          # or we can't kill it.
          my (%info) = lib_get_pid_info($pid);
          if (defined($info{'PID'}))
          {
            # The process is still alive.
            $retval = USM_FAIL;
            acfslib::lib_inform_print(9136,
                                     "PID %s could not be killed.", $pid);
            report_pid_info(%info);
          }
        }
      }
      else
      {
        $descriptor_list .= "$pid ";
      }

      #
      # A process can have multiple files open on the target mount
      # point. We only want to list or kill the process once.
      #

      $skipToNextProcess = 1;

    }
    close (LSOF);
  }

  # Split the string by spaces.
  my @words= split / /, $descriptor_list;
  # New hash.
  my %newwords;
  # Put the values in the string as the hash keys, assign a value of 1.
  # This prevents duplicates as they hash to the same value.
  for (@words) { $newwords{$_}=1 }
  # Join the keys back into a list.  This effectively removes duplicate
  # process id's in the string.
  $descriptor_list = join ' ', keys(%newwords);

  if ($action)
  {
    return $retval;
  }
  else
  {
    return $descriptor_list;
  }
} # end lib_osds_ mountpoint_descriptors

# lib_osds_control_devices_accessible
#
# We test the USM control device accessibility by opening them
# Note that this will work on all Linux/Unix - but not Windows
#
# return true (1) or false (0)
#
sub lib_osds_control_devices_accessible
{
  my ($ret) = 1;  # assume true

  open AVD, "<" . AVD_CTL_DEV or $ret = 0;
  if ($ret)
  {
    close AVD;
    # AVD open worked, now try OFS
    open OFS, "<" . OFS_CTL_DEV or $ret = 0;
    if ($ret)
    {
      # we can talk to both AVD and OFS control devices - success
      close OFS;
    }
  }
  return $ret;
} # end lib_osds_control_devices_accessible

# lib_osds_create_mount_point
#
# Create the mount point directory.
#
sub lib_osds_create_mount_point
{
  my $mount_point = shift;

  acfslib::lib_inform_print(9255, "Creating '%s' mount point.", $mount_point);
  eval File::Path::mkpath($mount_point);
  if ($@)
  {
    acfslib::lib_error_print(9256, "Failed to create mountpoint '%s'.", $@);
    return USM_FAIL;
  }
  return USM_SUCCESS;
}

# lib_osds_device_from_mountpoint
#
# return the device name given a mountpoint
#
sub lib_osds_device_from_mountpoint
{
  my ($mountpoint) = @_;
  my ($device) = "";
  my ($line);

  open (MOUNT, "mount |");
  while ($line = <MOUNT>)
  {
    my ($dev, $mntpt) = get_dev_mntpt_from_mount_line($line);    

    if ($mntpt eq $mountpoint)
    {
      $device = $dev;
      last;
    }
  }
  close (MOUNT);

  return $device;
} # end lib_osds_device_from_mountpoint

# lib_osds_get_advm_mounts
#
# return an doubly dimensioned array of devices and mountpoints
# of all currently mounted OFS file systems
# array element[0] is the device and array element[1] is the mountpoint
#
sub lib_osds_get_advm_mounts
{
  my (@array);
  my ($i) = 0;
  my ($line);

  open (MOUNT, "mount |");
  while ($line = <MOUNT>)
  {
    my ($device, $mount_point) = get_dev_mntpt_from_mount_line($line);    
    if ($device =~ /^\/dev\/asm\//)
    {
      push @{$array[$i]}, $device, $mount_point;
      $i += 1;
    }
  }
  close(MOUNT);
  return \@array;
} #end lib_osds_get_advm_mounts

# osds_get_asm_user
#
# Get the user name and group id that we use to connect to ASM
#
sub lib_osds_get_asm_user
{
  my (@out_array);
  my ($group);
  my ($line);

  if ($OSNAME eq "solaris")
  { 
    open(PS, "ps -eo user,gid,comm | grep asm_pmon | grep -v grep |");
  }
  elsif ($OSNAME eq "linux")
  {
    open(PS, "ps -eo user,gid,cmd | grep asm_pmon | grep -v grep |");
  }
  elsif ($OSNAME eq "aix")
  {
    # On AIX the asm_pmon process shows up as "oracle" whem using -o comm,
    # but ps -fe works - so we have to do it in 2 steps.
    open(PS, "ps -fe | grep asm_pmon | grep -v grep |");
    my ($psline) = <PS>;
    if (! $psline)
    {
      return (@out_array);
    }
    $psline =~ s/^\s+//; # Remove leading whitespace from the line
    my ($user, $pmon_pid) = split(/ +/, $psline);
    close(PS);
    if (defined($pmon_pid) ne "")
    {
      open(PS, "ps -eo user,gid,pid | grep $pmon_pid  | grep -v grep |");
    }
    else
    {
      return (@out_array);
    }
  }
  while ($line = <PS>)
  {
    # element 0 is the ASM user name, element 1 is the ASM gid.
    $line =~ s/^\s+//; # Remove leading whitespace from the line
    @out_array = split(/\s+/, $line);
    last;
  }
  close(PS);

  return (@out_array);
} # end lib_osds_get_asm_user

# lib_osds_run_as_user
#
# For now, ASM connections require euid of the ASM user - bug 6900692.
#
sub lib_osds_run_as_user
{
  my ($user_name, $cmd) = @_;
  my ($return_code);
  my (@args);

  ##### special case for usm_dbhome
  ##### usm_dbhome can be called as non-root - in which case we simmply pass
  ##### the command on without changing the user ID
  my ($euid) = $>;
  my ($asm_user) = lib_osds_get_asm_user();

  if (( $euid == 0) || ($user_name ne $asm_user))
  {
    @args = ('su', $user_name, '-c', "$cmd");
  }
  else
  {
    @args = $cmd;
  }
  $return_code = system(@args);

  return $return_code;

  #### once we drop usm_dbhome, the function can revert back to the below #####

  @args = ('su', $user_name, '-c', "$cmd");
  $return_code = system(@args);

  return $return_code;
}

# lib_osds_unmount
#
# unmount the specified file system
#
sub lib_osds_unmount
{
  my ($mountpoint) = @_;
  my ($result);
  my (@nfs_export_list) = acfslib::lib_osds_remove_exports($mountpoint);

  $result = system("umount $mountpoint 2> /dev/null");
  if ($result)
  {
    # The unmount failed. Restore previously existing NFS exports, if any.
    acfslib::lib_osds_restore_exports(@nfs_export_list);
    return USM_FAIL;
  }  
  return USM_SUCCESS;
} # end lib_osds_unmount


# lib_osds_usm_supported.
#
# The fact that we got here means that there is some support for
# this platform. However, perhaps not all releases are supported.
# We make that determination here.
#
# return true or false
#
sub lib_osds_usm_supported
{
  my ($arch) = $Config{archname};   # Machine architecture - e.g., i386
  chomp($arch);

  # For the time being, of all the "Unix" versions,
  # only Linux, AIX, and Solaris are supported
  if (!(($OSNAME eq "linux") || ($OSNAME eq "aix") || ($OSNAME eq "solaris")))
  {
    # TODO for other operating systems
    acfslib::lib_error_print(9125,
                            "ADVM/ACFS is not supported on this OS: '%s'.",
                            $OSNAME);
    return 0;
  }

  if (!(($arch =~ /^i686/) || ($arch =~ /^x86_64/)
        || ($arch =~ /^aix-thread-multi-64all/)
        || ($arch =~ /^i86pc/)
        || ($arch =~ /^sun4-solaris/)))
  {
    # TODO for other architectures
    acfslib::lib_error_print(9120,
                     "The '%s' machine architecture is not supported.", $arch);
    return 0;
  }

  # $type is "EL5", for example
  my ($type) = osds_acfslib::lib_osds_get_os_type();
  if ($type =~ /not supported/)
  {
    return 0;
  }
  return 1;
} # end lib_osds_usm_supported

# lib_osds_verify_usm_devices
#
# Verify that the USM drivers are loaded and running by checking the
# existence of the device control files for the drivers.
#
sub lib_osds_verify_usm_devices
{
  my ($ORACLE_HOME) = $ENV{ORACLE_HOME};
  my ($asmadmin) = acfslib::lib_get_asm_admin_name();

  # Make sure that the proper /dev files get created
  my ($found) = 0;
  my ($max_wait_seconds) = 60;

  my ($device) = AVD_CTL_DEV;
  acfslib::lib_inform_print(9156, "Detecting control device '%s'.", $device);

  while (($max_wait_seconds > 0) && (!$found))
  {
    if (! -e $device)
    {
      sleep 1;
      $max_wait_seconds -= 1;
    }
    else
    {
      $found = 1;
    }
  }

  if (!$found)
  {
    acfslib::lib_error_print(9121, "Failed to detect control device '%s'.", $device);
    return USM_FAIL;
  }
  else
  {
    # The ADVM driver creates /dev/asm/* and not (directly) /dev/asm.
    system("chgrp $asmadmin " . AVD_DIR);
    system("chmod 0770 " . AVD_DIR);
  }

  $max_wait_seconds = 60;
  $found = 0;
  $device = OFS_CTL_DEV;
  acfslib::lib_inform_print(9156, "Detecting control device '%s'.", $device);

  while (($max_wait_seconds > 0) && (!$found))
  {
    if (! -e $device)
    {
      sleep 1;
      $max_wait_seconds -= 1;
    }
    else
    {
      $found = 1;
    }
  }

  if (!$found)
  {
     acfslib::lib_error_print(9121, "Failed to detect control device '%s'.", $device);
    return USM_FAIL;
  }

  # We only warn if persistent logging can't be started.
  `$ACFSUTIL plogconfig -d $ORACLE_HOME`;
  if ($?)
  {
     acfslib::lib_inform_print(9225, "Failed to start OKS persistent logging.");
  }                
} #end lib_osds_verify_usm_devices


###### static routines only after this point ##########

# lib_get_pid_info
#
# Collect and return information, for a given PID.
# Returns an undefined %pid_hash if the PID is not found.
#
# Linux, Solaris, and AIX all support ps -fe and output in the same format.
# If a future supported Unix does not, we'll have to make changes.
#
sub lib_get_pid_info
{
  my ($pid) = @_;    # target PID
  my ($ps_info);     # output line from the ps(1) command
  my (%pid_hash);    # return: hashed output from the ps(1) command for $pid

  open PS, "/bin/ps -fe 2>&1 |"
          or warn ("Failed to run '/bin/ps -fe': $!"), return %pid_hash;
  # ps -fe format is:
  # UID PID PPID C STIME TTY TIME CMD
  #   0   1   2  3   4    5   6   7 
  while ($ps_info = <PS>)
  {
    my (@array) = split /\s+/, $ps_info;
    %pid_hash = (
      USER    => $array[0],
      PID     => $array[1],
      PPID    => $array[2],
      C       => $array[3],
      STIME   => $array[4],
      TTY     => $array[5],
      TIME    => $array[6],
      CMD     => $array[7],
    );

    if ($pid eq $pid_hash{'PID'})
    {
      close(PS);
      return %pid_hash;
    }
  }

  # Target PID not found.
  close (PS);
  undef %pid_hash;
  return %pid_hash;
} # end lib_get_pid_info

# 9999 messages are not formatted - WYSIWYG.
#
sub report_pid_info
{
  my (%info) = @_;
  acfslib::lib_inform_print(9141, "         COMMAND %s", $info{'CMD'});
  acfslib::lib_inform_print(9143, "         USER %s", $info{'USER'});
  acfslib::lib_inform_print(9144, "         CPU_TIME %s", $info{'TIME'});
  acfslib::lib_inform_print(9142, "         STATUS %s", $info{'C'});
}

# get_dev_mntpt_from_mount_line
#
# Get the device and mountpoint from a mount command output line.
#
# Different Unixs have different mount command output formats.
#  mount | grep mnt1
#    /dev/asm/foo-123 on /mnt1 ...           Linux
#    /dev/asm/foo-123 /mnt1    ...           AIX
#    /mnt1 on /dev/asm/foo-123 ...           Solaris
#
sub get_dev_mntpt_from_mount_line
{
  my ($line) = @_;
  my ($device, $on, $mountpoint);

  if ($OSNAME eq "linux")
  {
    # /dev/asm/xxx_yyy on /oracle_base/ofsdata/foo type ofs (rw)
    ($device, $on, $mountpoint) = split(/ /, $line);
  }
  elsif ($OSNAME eq "solaris")
  {
   # /dev/asm/xxx_yyy on /oracle_base/ofsdata/foo <options> on <date>
    ($mountpoint, $on, $device) = split(/ /, $line);
  }
  elsif ($OSNAME eq "aix")
  {
    # <node> /dev/asm/xxx_yyy /oracle_base/ofsdata/foo <type> <date> <options>
    my ($node);

    if ($line =~ /^ /)
    {
      # if the first char of the line is blank, we don't have a node.
      $line =~ s/^\s+//;                   # remove leading spaces
      $line =~ s/\s+/ /g;                  # remove extra spaces between fields
      ($device, $mountpoint) = split(/ /, $line);
    }
    else
    {
      $line =~ s/\s+/ /g;                  # remove spaces between fields
      ($node, $device, $mountpoint) = split(/ /, $line);
    }
  }
  else
  {
    # should never get here
    lib_error_print(9149,
             "unable to determine device mount status - unsupported OS name '%s'",
             $OSNAME);
  }

  return ($device, $mountpoint);
} # end get_dev_mntpt_from_mount_line

# This is a Windows function
sub lib_osds_get_drive_info
{
    return 0;
}

# lib_osds_is_abs_path
sub lib_osds_is_abs_path
{
  my $path = shift;
  if ( $path =~ /^\// )
  {
    return 1;
  }
  return 0;
}

# vim:ts=2:expandtab
