#
# $Header: usm/src/cmds/acfslib/win/osds_acfslib.pm /st_usm_11.2/15 2013/08/15 15:06:39 madoming Exp $
#
# osds_acfslib.pm
# 
# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
#
#
#    NAME
#      osds_acfslib.pm - Windows OSD library components.
#
#    DESCRIPTION
#      Purpose
#          Windows 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)
#    madoming    07/25/13 - Add support for W2012.
#    madoming    06/18/13 - Backport madoming_bug-16968069 from main
#    argardu     09/19/12 - Backport argardu_bug_14242006 from main
#    oehmoda     08/02/12 - Add lib_osds_check_uninstall_required.
#    plancian    06/07/12 - Backport plancian_bug-13739413 from main
#    madoming    01/16/12 - Backport averhuls_acfsdriverstate_validate from main
#    averhuls    11/23/11 - Avoid English text in "handle" output.
#    anjiwaji    10/25/11 - Backport anjiwaji_bug-12929243 from main
#    gsanders    08/30/11 - Backport gsanders_bug-12670596 from main
#    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
#    gsanders    06/30/11 - Backport gsanders_bug-12692110 from main
#    averhuls    06/17/11 - Add new return/exit codes.
#    averhuls    06/02/11 - XbranchMerge averhuls_persistent_log_nt from main
#    averhuls    05/18/11 - Start persistent logging after the drivers are
#                           loaded.
#    anjiwaji    02/25/11 - Add extra debugging when verifying if drivers are
#                           loaded.
#    gsanders    01/10/11 - XbranchMerge gsanders_bug-10229980 from main
#    gsanders    01/07/11 - Fix bug 10229980 clean bugs
#    plancian    10/28/10 - Added options hostlist=local NOHDR=true to kfod call
#    averhuls    10/05/10 - Further reduce the parsing of English text by using
#                           Win32::Service for starting and stopping drivers.
#    gsanders    09/24/10 - Use Win32::Service::GetStatus(),not sc, in query()
#    gsanders    09/18/10 - temporarily switch to English in lib_osds_mount()
#    gsanders    08/20/10 - add lib_osds_is_abs_path function
#    gsanders    08/18/10 - disallow relative pathnames for mount paths
#    jeabraha    08/18/10 - XbranchMerge jeabraha_2008r2_buildall from
#                           st_usm_11.2.0
#    gsanders    08/17/10 - Fix error checking for call to handle
#    jeabraha    08/16/10 - Update to 2008r2 binary location
#    gsanders    08/14/10 - lib_osds_mountpoint_descriptors quotemeta tweak
#    gsanders    08/08/10 - check for undefined field after split in query()
#    gsanders    07/14/10 - prefix lib_is_mounted with acfslib::
#    gsanders    07/07/10 - add lib_osds_create_mount_point
#    gsanders    06/22/10 - lib_osds_get_drive_info
#    gsanders    06/22/10 - lib_osds_is_mounted driver letter backslash check
#    agraves     06/14/10 - Change dismount processing to use lib_is_mounted to
#                           verify umount succeeded on Windows.
#    agraves     05/17/10 - XbranchMerge agraves_bug-9539710 from
#                           st_usm_11.2.0.1.0bundle1
#    gsanders    05/10/10 - Merge forward 11.2.0.1 work pass 2
#    averhuls    05/05/10 - Add $REDIRECT export that is needed for the 
#                           11.2.0.1 merge into MAIN.
#    gsanders    04/30/10 - Merge forward 11.2.0.1 work
#    averhuls    04/15/10 - Add Win2K8 support (R1 and R2).
#    agraves     04/14/10 - Change message number so that acfsus.msg builds
#                           again.
#    agraves     04/12/10 - Change message 9140 a bit.
#    agraves     04/09/10 - Convert 9999 message into real NLS message.
#    agraves     03/31/10 - Fix bug 9539710. This occurs if something that is
#                           accessing our path is mounted on a volume that goes
#                           away before we process it. abs_path will throw an
#                           error if the path does not exist. The solution is
#                           to check for the existence before abs_pathing.
#    averhuls    03/30/10 - Remove unused lib_osds_mountpoint_from_device().
#    averhuls    03/24/10 - If acfsutil has been deleted, fallback to the
#                           install area copy.
#    averhuls    03/17/10 - Add USM_NOT_SUPPORTED.
#    agraves     03/04/10 - Fix the dismount error checking for Windows so that
#                           it knows that it is not dismounted.  The return code
#                           was being lost before.
#                           Change the interface of WMI to use the WMI Perl
#                           module.  The 'wmic' command was hanging during
#                           execution.
#                           Change 'handle' to have /accepteula flag.  Without
#                           this, the first execution by a user requires that
#                           they accept a GUI EULA.  Obviously this does not
#                           work well under the CRS environment, which does not
#                           have a place to display the EULA.
#                           Change the parsing of the output from handle to
#                           corectly account for the (---) field in some of the
#                           returned code.
#                           Properly escape certain characters in the path so
#                           that perl does not see "C:\program files\blah" as
#                           c:\program.  This can cause us to kill the wrong
#                           items\programs.
#    agraves     01/19/09 - Remove 2>&1 redirection for execution in a windows
#                           crs environment.  For some reason it can't do it.
#    averhuls    01/12/10 - Use /f (force) on "acfsutil detach".
#    averhuls    01/08/10 - fix $prod_type for Windows Server 2008 R1.
#    averhuls    01/07/10 - Separate OS_TYPE into 2 separate fields - one to
#                           report to the user and one to find the install bits.
#    averhuls    01/04/10 - Correctly identify a Win 2008 system.
#    certan      11/30/09 - Detect WoW64 mode, which is active when running
#                           a 32-bit binary on a 64-bit architecture
#                         - Disable IA64 for now
#    certan      11/24/09 - In lib_osds_unload_driver, if we can't find
#                           acfsutil installed on the machine and we're in a
#                           development environment, try running acfsutil
#                           from the install files.  In this case, run the
#                           "detach" command for acfsutil with the "force"
#                           option, in case there are any version differences
#                           between acfsutil and the installed ACFS driver.
#    averhuls    11/18/09 - lib_osds_get_asm_user() also returns a group id.
#    agraves     07/28/09 - Remove lib_osds_is_db_home function.
#    averhuls    07/24/09 - Use full pathname for sc.exe.
#    averhuls    07/16/09 - convert '9999' message to a real NLS message.
#    averhuls    06/30/09 - Report information when a PID could not be killed.
#    averhuls    06/08/09 - Use kfod to check for the existence of ASM.
#    averhuls    06/01/09 - Export TMPDIR.
#    agraves     05/18/09 - Creation of lib_osds_is_db_home for windows
#    agraves     04/29/09 - Update messages to be consistent with acfsus.msg
#                           after Bill Manry's review.
#    averhuls    04/06/09 - Minor change to message 9120.
#    averhuls    02/20/09 - lib_osds_control_devices_accessible() let the 
#                           caller field and error messages.
#    averhuls    01/29/09 - rename usm_lib to acfslib.
#    agraves     01/28/09 - Add ACFSUTIL definition.
#    averhuls    01/22/09 - Typo fix.
#    averhuls    01/20/09 - Fix argument to message 9124.
#                         - Look in alternate location for acfsutil.
#    averhuls    12/22/08 - convert to use message catalog.
#    averhuls    12/16/08 - Move Win2000 to the unsuppported list.
#    averhuls    11/03/09 - Creation
#

require Win32API::File;
use strict;
use acfslib;
use Win32::OLE;
use Win32::Service;
use File::Path;
package osds_acfslib;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
                 lib_osds_check_uninstall_required
                 lib_osds_am_root
                 lib_osds_check_driver_inuse
                 lib_osds_check_driver_installed
                 lib_osds_check_driver_loaded
                 lib_osds_check_loaded_drivers_mismatch
                 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_is_mounted
                 lib_osds_load_driver
                 lib_osds_mountpoint_descriptors
                 lib_osds_mount
                 lib_osds_run_as_user
                 lib_osds_unload_driver
                 lib_osds_unmount
                 lib_osds_usm_supported
                 lib_osds_verify_usm_devices
                 lib_osds_is_abs_path 
                 @DRIVER_COMPONENTS
                 $ACFSUTIL
                 $ARCH
                 $OS_SUBDIR
                 $OS_TYPE
                 $REDIRECT
                 $TMPDIR
                 AVD_CTL_DEV
                 OFS_CTL_DEV
                 AVD_IDX
                 OFS_IDX
                 OKS_IDX
                 OPT_CHR
                 USM_FAIL
                 USM_SUCCESS
                 USM_SUPPORTED
                 USM_NOT_SUPPORTED
                 USM_REBOOT_RECOMMENDED
                 USM_TRANSIENT_FAIL
                 );

# 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;
use constant USM_SUPPORTED          => 5;

use constant OPT_CHR => "/";            # Windows option character

use constant AVD_CTL_DEV => "\\\\.\\.asm_ctl_spec";# really "\\.\.asm_ctl_spec"
use constant OFS_CTL_DEV => "\\\\.\\OFSCTL";       # ditto about the slashes 

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
our (@DRIVER_COMPONENTS) = (
           "oracle advm", "oracle oks", "oracle acfs",
           );

# Driver states
# See http://msdn.microsoft.com/en-us/library/ms685992(v=VS.85).aspx
use constant SERVICE_STOPPED          => 1;
use constant SERVICE_START_PENDING    => 2;
use constant SERVICE_STOP_PENDING     => 3;
use constant SERVICE_RUNNING          => 4;
use constant SERVICE_CONTINUE_PENDING => 5;
use constant SERVICE_PAUSE_PENDING    => 6;
use constant SERVICE_PAUSED           => 7;

# This removes output redirection on Windows.  
# For some reason, when run under the CRS environment, this causes
# scripts to fail with "cannot open file descriptor",
# or "cannot open pipe NOWAIT".
our ($REDIRECT)  = "";

our ($ARCH);
our ($OS_SUBDIR);
our ($OS_TYPE);
our ($ORACLE_HOME) = $ENV{ORACLE_HOME};
our ($ACFSUTIL) = "$ORACLE_HOME\\bin\\acfsutil.exe";
our ($TMPDIR) = "\\temp";

my ($system_root) = $ENV{SYSTEMROOT};

# lib_osds_am_root
#   Windows Oracle always runs in admin mode
#
sub lib_osds_am_root
{
  # Windows Oracle always runs in admin mode
  return 1;
} # end lib_osds_am_root

# lib_osds_check_driver_inuse
#
#  Note: this function does nothing in Windows.
#
sub lib_osds_check_driver_inuse
{
  my ($driver) = @_;

  # TODO - how can we do this???
  # According to Jerry, it can't be done. You just issue a stop and
  # if the driver's in use, the state will be STOP_PENDING instead of STOPPED

  # pretend the driver is not in use, that's all we can do.
  return 0;
} # end lib_osds_check_driver_inuse

# lib_osds_check_driver_installed()
#
sub lib_osds_check_driver_installed
{
  my ($driver) = @_;              # USM driver currently being examined
  my ($driver_found) = 0;         # as the name implies
  my ($retval) = 0;               # returned to the caller - assume no drivers

  $driver_found = query("installed", $driver);

  return $driver_found;
} # end lib_osds_check_driver_installed

# lib_osds_check_uninstall_required
#
sub lib_osds_check_uninstall_required
{
  my ($previous_install_detected_msg) = @_;
  my ($return_code);
  $return_code = acfslib::lib_check_any_driver_installed();
  if(($return_code) && ($previous_install_detected_msg))
  {
    acfslib::lib_inform_print(9312, "Existing ADVM/ACFS installation detected.");
  }
  return $return_code;
} # end lib_osds_check_uninstall_required

# lib_osds_check_driver_loaded
#
sub lib_osds_check_driver_loaded
{
  my ($driver) = @_;

  return query("running", $driver);
} # end lib_osds_check_drivers_loaded

# lib_osds_check_loaded_drivers_mismatch
#
# Determine whether or not the installed drivers match the drivers that
# are loaded in the kernel.
# Solaris only for now.

sub lib_osds_check_loaded_drivers_mismatch
{
    return 0;
}

# lib_osds_is_mounted
#
# check to see if the specified mount point is active
#
# TODO - right now we only handle OFS file systems
#
sub lib_osds_is_mounted
{
  my ($mount_point) = @_;           # mount point to test
  my ($mounted) = 0;                # assume not mounted
  my ($result);

  # Change any '/' in the path to '\\'.  
  # This is caused by the fvfs test.
  # Its mount point defaults to x\x\x/y.
  # We should be able to handle both cases, since I 
  # can't guarantee unix admins will type it the same
  # as windows folks will.
  #
  # So we can end up with x/x\y and \s\s and \s\s/x and \\s\\s
  $mount_point =~ s/\//\\\\/g;
  # now, if we have \\, convert it to a single \ for the following compare.
  $mount_point =~ s/\\\\/\\/g;
  # Normalize drive letter specification by removing trailing backslash
  $mount_point = substr ($mount_point,0,2)
    if ( length($mount_point) == 3 && substr($mount_point,1,2) eq ":\\" );
  # disallow relative pathnames
  if ( ! acfslib::lib_is_abs_path ( $mount_point ) )
  {
    acfslib::lib_error_print(9366,
        "Relative path for mount point '%s' is not supported.", $mount_point);
    return $mounted;
  }

  # TODO: This used to have a 2>&1 in it.  
  # Now it needs to have the failure case tested.
  # 
  # Does it?  We don't check the errors.
  #
  open(CHECK, "$ORACLE_HOME/bin/acfsmountvol $REDIRECT |");
  while ($result = <CHECK>)
  {
    # remove all white space
    chomp($result);
    $result =~ s/^\s*//;
    # Normalize drive letter specification by removing trailing backslash
    $result = substr ($result,0,2)
      if ( length($result) == 3 && substr($result,1,2) eq ":\\" );
    if ($result eq $mount_point)
    {
      # the mountpoint is found.
      $mounted = 1;
      last;
    }
  }
  close(CHECK);
  return $mounted;
} # end lib_osds_is_mounted

# lib_osds_control_devices_accessible
#
# We test the USM control device accessibility by opening them
#
# return true (1) or false (0)
#
sub lib_osds_control_devices_accessible
{
  my ($file_handle);

  # createFile - 'k' = keep the file if it exists (don't create)
  #            - 'e' - the file must already exist (fail if it doesn't)
  #            - 'q' - query (no read or write)

  # see if we can open the ADVM device
  $file_handle = Win32API::File::createFile(AVD_CTL_DEV, "q ke");
  if ($file_handle <= 0)
  {
    my ($device) = "ADVM";
    # acfslib::lib_error_print(9121, "Failed to detect control '%s'.", $device);
    return 0;
  }
  Win32API::File::CloseHandle($file_handle);

  # see if we can open the ACFS device
  $file_handle = Win32API::File::createFile(OFS_CTL_DEV, "q ke");
  if ($file_handle <= 0)
  {
    my ($device) = "ACFS";
    # acfslib::lib_error_print(9121, "Failed to detect control '%s'.", $device);
    return 0;
  }
  Win32API::File::CloseHandle($file_handle);

  return 1;
} # 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;
  my $create_mount_path = 0;
  my $drive_info        = 0;
  my $drive_letter      = "";
  my $folder            = "";

  # 
  #  Extract the drive letter from the mount path, if any.
  #

  if ( $mount_point =~ /^[a-zA-Z]:/ )
  {
      # We have a drive letter.  See what we can find out about
      # it.
      $drive_letter = uc(substr($mount_point,0,1));
      $drive_info = lib_osds_get_drive_info ($drive_letter);
  }

  #
  #  Extract the folder part of the path, if any.
  #

  if ( $drive_letter eq "" )
  {
    $folder = $mount_point;
  }
  else
  {
    $folder = $mount_point;
    # Strip drive letter leaving single leading "\"
    $folder =~ s/^.:\\*/\\/;
    # If folder is "\", treat it like a drive letter. i.e. no
    # folder specified.
    $folder = "" if ( $folder eq "\\" );
  }

  #
  #  Verify that if we have a drive letter and a folder the 3rd
  #  char is a backslash.  This is invalid: C:dbhome.  This is
  #  valid: C:\dbhome
  #

  if ( $drive_letter ne "" &&
       $folder ne "" && 
       substr($mount_point,2,1) ne "\\" )
  { 
      acfslib::lib_error_print(9000,
          "Invalid drive letter specification: %s", $mount_point);
      return USM_FAIL;
  }

  #
  #  There are some rules governing mount point directory creation.
  #

  if ( $drive_letter ne "" && $folder ne "" )
  {
    # Rule 1: If we have a drive letter and we're mounting on a
    # folder, create the folder.  Note that the drive letter must
    # exist and be a volume.
  
    if ( $drive_info && $drive_info->{FileSystem} ne "" )
    {
        # The drive exists and has a file system on it.
        $create_mount_path = 1;
    }
    else
    {
      if ( ! $drive_info )
      {
        # Drive doesn't exist
        acfslib::lib_error_print(10285,
          "Pathname '%s' does not exist.", $drive_letter . ":" );
        return USM_FAIL;
      }
      else
      {
        # drive exists but isn't an FS      
        acfslib::lib_error_print(9999,
          "A folder was specified but ${drive_letter}: " .
          "does not contain a filesystem." );
        return USM_FAIL;
      }
    }
  }  
  elsif ( $drive_letter ne "" && $folder eq "" )
  {
    # Rule 2: If we have only a drive letter and no folder is specified,
    # acfsmountvol will mount directly onto the driver letter.  The
    # drive letter must not exist.
  
    if ( $drive_info )
    {
      # The drive exists.  This is an error.  The user is asking
      # us to mount on <x>: We have no idea what <x:> is being
      # used for.  We don't want to mount over it. acfsmountvol
      # will throw an error.
      $create_mount_path = 0;
    }
    else
    {
      # The drive doesn't exist (good). acfsmountvol will mount
      # directly onto the drive.
      $create_mount_path = 0;
    }
  }
  elsif ( $drive_letter eq "" && $folder ne "" )
  {
    # Rule 3: If a folder is specified without a drive letter, create
    # the folder on the current drive.  Note that it is an error to
    # specify a folder without a mount path but we'll handle it.  It
    # should have been thrown out above us.
    $create_mount_path = 1;
  }

  #  Create the mount path if applicable.
  if ( $create_mount_path  )
  {
    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 mount point
#
sub lib_osds_device_from_mountpoint
{
  my ($mountpoint) = @_;
  my ($device);
  my ($prev_str);
  my ($str);

  open (DEV, "$ORACLE_HOME/bin/acfsmountvol |");
  while ($str = <DEV>)
  {
    $str =~ s/\s+//;
    chomp($str);

    if ($str eq $mountpoint)
    {
      $device = lc($prev_str);
      last;
    }

    $prev_str = $str;
  }

  close (DEV);

  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
#
# TODO - Right now we can only do acfs mounts - not all advm mounts
#
sub lib_osds_get_advm_mounts
{
  my (@array);
  my ($result);
  my ($index) = 0;
  my ($have_device) = 0;
  my ($device, $mount_point);

  open (MOUNT, "$ORACLE_HOME/bin/acfsmountvol |");
  while ($result = <MOUNT>)
  {
    # A mount entry looks like (for example):
    #       <blank line>
    #       asm_dg_vol
    #           e:\mnt

    # remove leading and trailing space
    chomp($result);
    $result =~ s/^\s+//;

    if ($result eq "")
    {
      $have_device = 0;
      next;
    }
    if ($have_device == 0)
    {
      # our last result was a blank line so this has to be the device
      $device = $result;
      $have_device = 1;
    }
    else
    {
      # our last result was the device so this has to be the mountpoint
      $mount_point = $result;

      push @{$array[$index]}, $device, $mount_point;
      $index += 1;
      $have_device = 0;    # just in case
    }
  }
  close(MOUNT);
  return \@array;
} # end lib_osds_get_advm_mounts

# Get the oracle binary user name
#
sub lib_osds_get_asm_user
{
  my ($username);
  my ($KFOD);
  my ($ret);
  my ($ADE_VIEW_ROOT) = $ENV{ADE_VIEW_ROOT};
  my (@out_array);

  if (defined($ADE_VIEW_ROOT))
  {
    $KFOD = "$ADE_VIEW_ROOT/oracle/rdbms/bin/kfod hostlist=local NOHDR=true";
  }
  else
  {
    $KFOD = "$ORACLE_HOME/bin/kfod hostlist=local NOHDR=true";
  }

  open(KFOD, "$KFOD |");
  chomp(my $kfodout = <KFOD>);
  close (KFOD);
  $ret = $?;
  # print "lib_get_get_asm_user: kfod return code = $ret\n";

  if ($ret ne 0)
  {
    # return an undefined value to signify that ASM is not running.
    $out_array[0] = $username;
  }
  elsif ($kfodout eq "")
  {
    # return an undefined value to signify that ASM is not running.
    $out_array[0] = $username;
  }
  else
  {
    # return a defined value.
    $out_array[0] = "admin";
  }
  $out_array[1] = 0;          # use 0 as the NT gid.

  return (@out_array);
} # end lib_osds_get_asm_user

# lib_osds_load_driver()
#
sub lib_osds_load_driver
{ 
  my ($driver) = @_;

  return common_driver_load_unload("start", $driver);
} # end lib_osds_load_driver

# 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.
#
use Cwd 'abs_path';
use File::Basename;

sub lib_osds_mountpoint_descriptors
{
  my ($mountpoint, $action) = @_;
  my ($handle) = "$ORACLE_HOME/bin/handle.exe";
  my ($len_mountpoint);        # mount point string length
  my ($device);                # mount point ADVM device name
  my ($have_device);           # found the mount point device
  my ($str);                   # temporary "working" variable
  my ($descriptor_list);       # list of open desctiptors
  my ($prog_name);             # executable program name
  my ($prog_path);             # Path of the binary
  my ($file_path);             # path of open file
  my ($pid) = "";              # process ID from sysinternals::Handle
  my ($retval) = USM_SUCCESS;  # return value

  # Normalize drive letter specification by removing trailing backslash
  $mountpoint = substr ($mountpoint,0,2)
    if ( length($mountpoint) == 3 && substr($mountpoint,1,2) eq ":\\" );

  # Escape the mountpoint characters.
  $mountpoint = quotemeta(lc($mountpoint));

  # get the device from the mount point
  open (DEV, "$ORACLE_HOME/bin/acfsmountvol |");
  while ($str = <DEV>)
  {
    $str =~ s/\s+//;
    chomp($str);
    # Don't store the quotemeta in this one cause we want the dev un meta'd 
    # for later compares.
    $str = lc($str);
    if (quotemeta($str) eq $mountpoint)
    {
      $have_device = 1;
      $len_mountpoint = length($mountpoint);
      last;
    }
    else
    {
      $device = lc($str);
    }
  }
  close (DEV);

  if (!defined($have_device))
  {
    acfslib::lib_error_print(9122,
            "ADVM device not determined from mount point '%s'.", $mountpoint);
    if ($action)
    {
      return $retval;
    }
    else
    {
      return $descriptor_list;
    }
  }

  #### First we kill all the open file handles on the mount point

  # TODO: Figure a way to check the error case here, 
  # this used to have a 2>&1 in it.
  # It was removed for Windows execution in a CRS enviroment, which 
  # seems to choke on the redirection.
  # Or does it?  Perhaps it was choking on the eula.  Revisit this in MAIN.
  # 
  # Do we not want to die if we can't open this?
  # Accept the eula so that it doesn't hang up CRS, waiting for GUI input.

  if (! -e $handle)
  {
    acfslib::lib_error_print(9123, "%s command not found.", $handle);
    if ($action)
    {
      return $retval;
    }
    else
    {
      return $descriptor_list;
    }
  }

  open (HANDLE, "$handle -a $REDIRECT /accepteula |");
  if ( $! != 0 )
  {
    acfslib::lib_error_print(9138,
                             "command '%s' completed with an error: %s",
                             $handle, $!);
    if ($action)
    {
      return $retval;
    }
    else
    {
      return $descriptor_list;
    }
  }
  while($str = <HANDLE>)
  {
    chomp $str;
    $str =~ s/^\s*//; # strip leading space

    # handle -a output consists of 3 or more lines per entry
    # line 1: ----------------------------------------------
    # line 2: <executable> pid: <pid> <domain>
    # line 3: {tab} <handle>: <type> <name>
    # -- or --
    # line 3: {tab} <handle>: <type> (---) <name>
    # line x: if it exists, repeat of line 3 format
    my @fields = split ( /\s+/, $str );

    #
    #  Check for the "line 1" case: a row of dashes.
    #

    next if ( $str =~ "^-+\$" );
    
    #
    #  Check for "line 2" case: a line containing a pid and program name
    #  Example format:
    #   gvim.exe pid: 4980 NEDCDOMAIN\gsanders
    #

    if ($fields[1] eq "pid:")
    { 
      $pid = $fields[2];
      $prog_name = $fields[0];
      # The next line should be a "line 3" line.  It should contain a
      # handle and path.
      next;
    }

    #
    #  This is either a "line 3" or a line of Handle's front matter
    #  (i.e, version info, copyright, sysinternals web address, etc).
    #  If we don't have a pid yet, we're looking at front matter so
    #  move past it.
    #

    next if ( $pid == "" );

    #
    #  If we get here we have a "line 3" line containing a handle and
    #  path of open file.  Here's some format examples:
    #    2F0: File (RWD) \Device\Asm\asm-CRSDG1VOL1-279\foobar.txt
    #    280: File (RW-) C:\.foobar.txt.swp
    #  Double check that we have a handle field.
    #

    next if ( $fields[0] !~ /^\s*[0-9A-F]+:$/ );

    #
    #  We have a "line 3" line. It contains a handle and path.
    #

    $file_path = lc ($fields[-1]);

    #  Don't quotemeta this, as it could be either a device:
    #  \Device\Asm\asm-TEST1-54
    #  - or -
    #  a mountpoint:
    #  c:\blah\blah

    if ( ( $file_path =~ /\\device\\asm\\$device.*/i ) ||
         ( $file_path =~ /^$mountpoint/ ) )
    {
      if ($action == 1)
      {
        acfslib::lib_inform_print(9126,
            "Attempting to terminate the program '%s' with OS process ID '%s'.",
            $prog_name, $pid);
        $retval = system("tskill $pid");
        if ($retval)
        {
          my (%info) = get_pid_info($pid);
          if (defined($info{'PID'}))
          {
            acfslib::lib_inform_print(9136,"PID %s could not be killed.",$pid);
            report_pid_info(%info);
          }
          else
          {
            # the PID is no longer found
          }
        }
      }
      else
      {
        #print "prog_name $prog_name pid $pid prog_directory $prog_directory\n";
        $descriptor_list .= "$pid ";
      }
    }
  }
  close (HANDLE);

  ##### handle.exe doesn't get us the device name of running executables
  ##### so we have to find those separately.  

  # If we cannot access WMI, we just keep going.
  my $processes = Win32::OLE->GetObject("winmgmts:")->InstancesOf("Win32_Process");
  for my $proc (in $processes)
  {
    $str =~ s/\s+/ /g;
    chomp($str);
    my (@str_array) = split(/ /, $str);
    my ($count) = 0;

    $prog_name = $proc->{Name};
    $prog_path = $proc->{ExecutablePath};
    $pid = $proc->{ProcessId};

    # ignore the "impossible"
    if ($prog_path =~ /^\\SystemRoot/)
    {
      next;
    }

    # ignore items without an ExecutablePath
    if ($prog_path eq "")
    {
      next;
    }

    # abs_path(), below, doesn't like drive letters in the name so we strip
    # it off - if it exists. NBD, abs_path() will put it back.
    if ($prog_path =~ /:/)
    {
      my @str_array = split(/:/, $prog_path);
      $prog_path = $str_array[1];
    }

    # get the mount point of the executable.
    # 1. strip off the file name (required for abs_path().
    # 2. get path name of the directory including the drive letter.
    # 3. trim off the trailing "fluff".
    # 4. convert to lower case to match $mountpoint format.
    # 5. reverse the '/' that abs_path() inserted after the ':'.
    # 6. Quote meta characters, such as \ 
    
    if (-e $prog_path)
    {
      $prog_path = dirname($prog_path);                    #1
      $prog_path = abs_path($prog_path) or next;           #2
      $prog_path = substr($prog_path, 0, $len_mountpoint); #3
      $prog_path = lc($prog_path);                         #4
      $prog_path =~ s/\//\\/;                              #5
      $prog_path = quotemeta($prog_path);                  #6
    }

    if ($mountpoint eq $prog_path)
    {
      if ($action == 1)
      {
        # TODO: Spit out message we are terminating a process.
        $retval = $proc->Terminate();
        if ($retval)
        {
            $pid = $proc->{CommandLine};
            my (%info) = get_pid_info($pid);
            acfslib::lib_inform_print(9136,"PID %s could not be killed.",$pid);
            report_pid_info(%info);
        }
      }
      else
      {
        # print "prog_name $prog_name location $prog_directory pid $pid\n";
        $descriptor_list .= "$pid ";
      }
    }
  }

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

# lib_osds_mount
#
# Mount the specified file system
#
sub lib_osds_mount
{
  my ($device, $mount_point, $options) = @_;
  my ($result);
  my $nlsLangSave                      = $ENV{NLS_LANG};
  my $status                           = USM_FAIL;

  # We are looking for the English word, "Successfully" to determine if
  # the mount worked.

  $ENV{NLS_LANG} = "english";

  # We ignore the mount options since there are only 3 and none apply
  #   /a - mount all volumes in the registry - nope, don't want that
  #   /h - print help info - nope, don't want that
  #   /v - verbose mode - nope, don't want that

  open (MOUNT, "$ORACLE_HOME/bin/acfsmountvol $mount_point $device |");
  while ($result = <MOUNT>)
  {
    if ($result =~ /Successfully/)
    {
      $status = USM_SUCCESS;
      last;
    }
    # debug
    # print "$result\n";
  }
  close(MOUNT);

  # restore native language setting
  $ENV{NLS_LANG} = $nlsLangSave;

  return $status;

} # end lib_osds_mount

# lib_osds_run_as_user
#
# Not used needed for Windows - basically a pass through stub
sub lib_osds_run_as_user
{
  my ($user_name, $cmd) = @_;
  my ($return_code);

  # strip off leading "./" if it exists
  $cmd =~ s/^\.\///;
  system($cmd);
  # Check the return code for 
  # failure to execute.  I don't check for signals (& 127).
  if ( $? == -1 )
  {
    $return_code = -1;
  } 
  else 
  {
    $return_code = $? >> 8;
  }
  return $return_code;
} # end lib_osds_run_as_user

# lib_osds_unload_driver()
#
# ACFS is a registered file system and needs to be de-registered before
# the driver can be stopped. ofsutil detach does this.
#
sub lib_osds_unload_driver
{
  my ($driver, $install_files_loc) = @_;
  my ($detach_cmd);

  if ($driver eq $DRIVER_COMPONENTS[OFS_IDX])
  {
    my ($retval);

    # ACFS driver
    #
    # We look for acfsutil in the installed location first. If that fails, we
    # use the acfsutil in the install source location(s). This can either be
    # in the shiphome or, if we are in a development environment, the view or
    # \usm\bin. We run with the "force" detach option so that we won't abort
    # in case there are version differences between acfsutil and the
    # installed ACFS driver)
    if(-e $ACFSUTIL)
    {
      $detach_cmd = "$ACFSUTIL detach /f";
    }
    elsif(-e "/usm/bin/acfsutil.exe")
    {
       $detach_cmd = "/usm/bin/acfsutil.exe detach /f";
    }
    elsif(defined($install_files_loc) &&
         (-e $install_files_loc . "/acfsutil.exe"))
    {
       $detach_cmd = $install_files_loc . "/acfsutil.exe detach /f";
    }

    if (!defined($detach_cmd))
    {
      acfslib::lib_error_print(9123, "'%s' command not found", "acfsutil");
      return USM_FAIL;
    }

    $retval = system ($detach_cmd);
    if ($retval)
    {
      my ($driver) = "ACFS";
      acfslib::lib_error_print(9124, "%s driver failed to detach from the " .
                                     "system driver stack.", $driver);
      return USM_FAIL;
    }
  }

  return common_driver_load_unload("stop", $driver);
} # end lib_osds_unload_driver

# lib_osds_unmount
#
# unmount the specified file system
#
# TODO - right now we only handle OFS file systems
#
sub lib_osds_unmount
{
  my ($mountpoint) = @_;
  my ($result);
  my ($ret_val) = USM_SUCCESS;

  # the failure case here must be tested.
  # only the "does it work" case has been tested.
  # TODO: test this failure case.
 
  # This used to have a 2>&1 in it.
  # But CRS on Windows doesn't seem to like that format. 
  # We don't check the return code of dismount for anything. To
  # verify that the FS is actually unmounted, go looking for the
  # mount.  If it's still there the unmount failed.
  system("$ORACLE_HOME/bin/acfsdismount $mountpoint");
  if (acfslib::lib_is_mounted($mountpoint) )
  {
     # we got an error if we got here
     $ret_val = USM_FAIL;
  }
  return $ret_val;
} # 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
{
  # From:
  #   http://kobesearch.cpan.org/htdocs/Win32/Win32.pm.html#Win32_GetOSVersion
  #
  # Note: that you MUST have at least Perl V5.10 to get correct values
  # for Windows 7 and Server 2008.
  # 
  # Win32::GetOSVersion()
  # OS                    ID    MAJOR   MINOR  PRODUCT_TYPE
  # Win32                  0      -       -         -
  # Windows 95             1      4       0         -
  # Windows 98             1      4      10         -
  # Windows Me             1      4      90         -
  # Windows NT 3.51        2      3      51         -
  # Windows NT 4           2      4       0         -
  # Windows 2000           2      5       0         -
  # Windows XP             2      5       1         -
  # Windows Server 2003    2      5       2         -
  # Windows Vista          2      6       0         1
  # Windows Server 2008 R1 2      6       0         3
  # Windows 7              2      6       1         1
  # Windows Server 2008 R2 2      6       1         3
  # Windows 8              2      6       2         1
  # Windows Server 2012    2      6       2         3

  my ($desc, $major, $minor, $build, $id, undef, undef, undef, $prod_type) =
                                                        Win32::GetOSVersion();

  $ARCH = $ENV{PROCESSOR_ARCHITECTURE};

  if ($ARCH eq "x86")
  {
    # Detect WoW64 mode, which is active when running a 32-bit binary
    # on a 64-bit architecture
    if (defined($ENV{PROCESSOR_ARCHITEW6432}) &&
        $ENV{PROCESSOR_ARCHITEW6432} eq "AMD64") 
    {
      $ARCH = "x86_64";
    }
    else
    {
      $ARCH = "i386";
    }
  }
  elsif (($ARCH eq "x64") || ($ARCH eq "AMD64"))
  {
     $ARCH = "x86_64";
  }
#  IA64 is presently unsupported
#  elsif ($ARCH eq "IA64")
#  {
#     $ARCH = "ia64";
#  }
  else
  {
    # TODO for other architectures
    acfslib::lib_error_print(9120,
                     "The '%s' machine architecture is not supported.", $ARCH);
    return 0;
  }

  # parse GetOSVersion() results - see subroutine header
  if (($id eq 2) && ($major eq 5) && ($minor eq 1))
  {
    $OS_TYPE = "Windows XP";
    $OS_SUBDIR = "XP";
  }
  elsif (($id eq 2) && ($major eq 5) && ($minor eq 2))
  {
    $OS_TYPE = "Windows Server 2003";
    $OS_SUBDIR = "2003";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 0) && ($prod_type == 3))
  {
    $OS_TYPE = "Windows Server 2008 R1";
    $OS_SUBDIR = "2008";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 1) && ($prod_type == 3))
  {
    $OS_TYPE = "Windows Server 2008 R2";
    $OS_SUBDIR = "2008R2";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 2) && ($prod_type == 3))
  {
    $OS_TYPE = "Windows Server 2012";
    $OS_SUBDIR = "2012";
  }

  # The above platforms are all that we support for now.
  if (defined($OS_TYPE))
  {
    return 1;
  }

  ##### We have a non-supported Windows version. Identify it.

  if (($id eq 1) && ($major == 4) && ($minor == 0))
  {
    $OS_TYPE = "Windows 95";
  }
  elsif (($id eq 1) && ($major == 4) && ($minor == 10))
  {
    $OS_TYPE = "Windows 98";
  }
  elsif (($id eq 1) && ($major == 4) && ($minor == 90))
  {
    $OS_TYPE = "Windows Me";
  }
  elsif (($id eq 2) && ($major == 3) && ($minor == 51))
  {
    $OS_TYPE = "Windows NT 3.51";
  }
  elsif (($id eq 2) && ($major == 4) && ($minor == 0))
  {
    $OS_TYPE = "Windows NT 4";
  }
  elsif (($id eq 2) && ($major == 5) && ($minor == 0))
  {
    $OS_TYPE = "Windows 2000";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 0) && ($prod_type == 1))
  {
    $OS_TYPE = "Windows Vista";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 1) && ($prod_type == 1))
  {
    $OS_TYPE = "Windows 7";
  }
  elsif (($id eq 2) && ($major == 6) && ($minor == 2) && ($prod_type == 1))
  {
    $OS_TYPE = "Windows 8";
  }
  
  if (defined($OS_TYPE))
  {
    acfslib::lib_error_print(9125,
                            "ADVM/ACFS is not supported on this OS: '%s'",  
                            $OS_TYPE);
  }
  else
  {
    acfslib::lib_error_print(9125,
                            "ADVM/ACFS is not supported on this OS: '%s'",  
                            "unrecognized OS");
    acfslib::lib_error_print
      (9140, "Unable to determine the correct drivers for this version of Windows: ID:%s Major:%s Minor:%s Product Type:%s",

              $id, $major, $minor, $prod_type);
  }

  return 0;

} # end lib_osds_usm_supported

# lib_osds_verify_usm_devices
#
sub lib_osds_verify_usm_devices
{
  my ($driver);                   # USM driver currently being examined
  my ($num_drivers_loaded) = 0;   # as the name implies
  my ($retval) = 0;               # returned to the caller - assume no drivers
  my ($file_handle);

  # make sure all drivers are loaded (running in Windows speak)
  foreach $driver ($DRIVER_COMPONENTS[OKS_IDX],
                   $DRIVER_COMPONENTS[AVD_IDX], $DRIVER_COMPONENTS[OFS_IDX])
  {
    acfslib::lib_inform_print(9157, "Detecting driver '%s'.", $driver);
    $num_drivers_loaded += query("running", $driver);
  }

  if ($num_drivers_loaded != 3)
  {
    acfslib::lib_error_print(9127,
                               "Not all ADVM/ACFS drivers have been loaded.");
    return USM_FAIL;
  }

  if (!lib_osds_control_devices_accessible())
  {
    # lib_osds_control_devices_accessible will print specific errors
    return USM_FAIL;
  }

  # start the persistent log
  # 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.");
  }

  return USM_SUCCESS;
} #end lib_osds_verify_usm_devices

######################################
## internal static functions
######################################

sub query
{
  my ($query_type, $driver) = @_;
  my ($retval)      = 0;
  my ($have_driver) = 0;
  my %driver_status;

  if ( Win32::Service::GetStatus( "", $driver, \%driver_status) == 0 )
  {
    # Driver not installed.
    # Warning: We can get here if the driver is in the "STOP_PENDING"
    # state.  Maybe this is a bug in Win32::Service.
    # Shouldn't matter to what we're doing ...
    return 0;
  }

  if ($query_type eq "running")
  {
    if ( $driver_status{CurrentState} == SERVICE_RUNNING )
    {
      $retval = 1;
    }
    elsif ( $driver_status{CurrentState} == SERVICE_STOP_PENDING )
    {
      # The driver is not stopped. Likely it is in use.
      $retval = 1;
    }
  }
  elsif ($query_type eq "installed")
  {
    # The fact that Win32::Service found the driver is sufficient.
    $retval = 1;
  }
  elsif ($query_type eq "query")
  {
    # Return the driver state
    return $driver_status{CurrentState};
  }
  else
  {
    acfslib::lib_error_print(9128, "unknown query type '%s'",  $query_type);
  }

  return $retval;
}

# get_pid_info
#
# Collect and return information, for a given PID.
# Returns an undefined %pid_hash if the PID is not found.
#
sub get_pid_info
{
  my ($pid) = @_;   # target PID
  my ($task_info);  # output line from the tasklist / command
  my (%pid_hash);   # return: hashed output from the tasklist command for $pid

  # This used to have a 2>&1 in it.
  # TODO: Figure a way to check for the error of tasklist here.
  #
  # This was removed since CRS on Windows seems to disallow the redirection.
  open TL, "tasklist /v |"
          or warn ("Failed to run 'tasklist /v': $!"), return %pid_hash;
  # tasklist /v format is:
  # COMMAND PID SESSION SESSION# MEM K STATUS USER -- varies (see below) --  
  #   0      1     2       3      4  5   6     7     8    9     10 
  while ($task_info = <TL>)
  {
    my (@array) = split /\s+/, $task_info;
    %pid_hash = (
      COMMAND => $array[0],
      PID     => $array[1],
      SESSION => $array[2],
      SESS_NUM=> $array[3],
      MEM     => $array[4] . $array[5],
      STATUS  => $array[6],
    );

    if ($array[9] eq "SERVICE")
    {
      # e.g., "NT AUTHORITY\NETWORK SERVICE"
      $pid_hash{'USER'} = $array[7] . " $array[8]" . " $array[9]";
      $pid_hash{'CPU_TIME'} = $array[10];
      $pid_hash{'TITLE'} = $array[11];
    }
    elsif ($array[8] =~ "SYSTEM")
    {
      # e.g., "NT AUTHORITY\SYSTEM"
      $pid_hash{'USER'} =  $array[7] . " $array[8]";
      $pid_hash{'CPU_TIME'} = $array[9];
      $pid_hash{'TITLE'} = $array[10];
    }
    else
    {
      # e.g., "FOODOMAIN\user"
      $pid_hash{'USER'} = $array[7];
      $pid_hash{'CPU_TIME'} = $array[8];
      $pid_hash{'TITLE'} = $array[9];
    }


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

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

sub report_pid_info
{
  my (%info) = @_;
  acfslib::lib_inform_print(9141, "         COMMAND %s", $info{'COMMAND'});
  acfslib::lib_inform_print(9142, "         STATUS %s", $info{'STATUS'});
  acfslib::lib_inform_print(9143, "         USER %s", $info{'USER'});
  acfslib::lib_inform_print(9144, "         CPU_TIME %s", $info{'CPU_TIME'});
  acfslib::lib_inform_print(9145, "         MEM %s", $info{'MEM'});
}

# common_driver_load_unload()
#
# Use Win32::Service to start or stop the specified driver.
# 
sub common_driver_load_unload
{ 
  my ($action, $driver) = @_;
  my ($ret)             = USM_FAIL;
  my $driverState       = 0;
  my ($timeoutMax) = 1200;
  my ($timeout) = 0;
  
  if ($action eq "start")
  {
    if ((query("running", $driver)) ||
        (Win32::Service::StartService("", $driver)))
    {
      # driver already running or was started
      $ret = USM_SUCCESS;
    }
  }
  else # ($action eq "stop")
  {
    $driverState = query ( "query", $driver );

    if ( $driverState == SERVICE_RUNNING )
    {
        if ( ! Win32::Service::StopService("", $driver) )
        {
          acfslib::lib_error_print(9456,
             "An attempt to stop the driver %s failed.", $driver);
          return USM_FAIL;
        }
    }

    if ( $acfslib::CRS_ACTION eq "clean" )
    {
        # For clean we fire stop requests at the drivers in rapid
        # succession without regard for any STOP_PENDING states that
        # might be encountered.
        return USM_SUCCESS;
    }

    #
    # Wait for driver to stop.  In this way we serialize the driver
    # stops and thus increase our chance of success.   This prevents us
    # from stopping a driver while a driver that depends on it is in the
    # "STOP_PENDING" state.
    #

    $driverState = query ( "query", $driver );
    while (( $driverState != SERVICE_STOPPED ) && ($timeout < $timeoutMax))
    {
        sleep 10;
        $timeout += 10;
        acfslib::lib_inform_print(9291,
          "Waiting for the Windows 'sc stop %s' command to complete.",
          $driver);
        $driverState = query ( "query", $driver );
    }
    return USM_FAIL if ($timeout >= $timeoutMax);
    
    $ret = USM_SUCCESS;

  }
  return $ret;
} # end common_driver_load_unload

# lib_osds_get_drive_info
#   Get info for the drive letter.  Return a reference to a hash
#   containing the following elements:
#       Path
#       DriveLetter
#       ShareName
#       DriveType
#       RootFolder
#       AvailableSpace
#       FreeSpace
#       TotalSize
#       VolumeName
#       FileSystem
#       SerialNumber
#       IsReady

sub lib_osds_get_drive_info
{
    my $drive_letter = shift;
    my $fs = Win32::OLE->new("Scripting.FileSystemObject");
    $drive_letter = uc($drive_letter); # $fs->Drives->DriveLetter are uc
    foreach my $drv ( Win32::OLE::in($fs->Drives) )
    {
        return $drv if ( $drv->{DriveLetter} eq $drive_letter );
    }
    return 0;
}

#
# lib_osds_is_abs_path 
#    Note that we do not support mounting on a relative absolute path
#    like \blah\blah\blah.  The path must include a drive letter to be
#    consider absolute as in X:\blah\blah\blah.
#
sub lib_osds_is_abs_path
{
  my $path = shift;
  if ( $path =~ /^[[:alpha:]]:/ )
  {
    return 1;
  }
  return 0;
}

1;
# vim:ts=2:expandtab
