# 
#
# acfsregistrymount.pl
# 
# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      acfsregistrymount.pl
#
#    DESCRIPTION
#      Mount OCR registered USM file systems. Unmount USM file systems.
#
#    USAGE
#      acfsregistrymount {start|check} [-d diskgroup] [-v volume] [-h] [-s]
#          mount file systems registered in the OCR
#          if "diskgroup" is specified mount only filesystems in that dg.
#          Ditto for volumes. "check" like start but without the initialization
#          Most of the real work is actually done by the check function -
#          which is called at the end of the start function.
#      acfsregistrymount {stop|clean} [-d diskgroup] [-v volume] [-h] [-s]
#          unmount USM file systems. "clean" forces umounts that have opens.
#          if "diskgroup" is specified umount only filesystems in that dg.
#          Ditto for volumes.
#
#    EXIT STATUS
#      CLSAGFW_* - see has/src/crs/agentfw/include/clsagfw.h
#
#    NOTES
#      This command is expected to be called from CRS after ASM has been 
#      started and the USM drivers loaded. we make no explicit checks here.
#
#      This command is called as root but things like ASM mounting 
#      diskroups and enabling volumes has to be done by a user with asmadmin
#      privs. This is an ASM bug that is marked for 11.2 fix.
#
#      All user output should be done in this file to ensure cross platform
#      similarity in command look and feel.
#
# 

use strict;
use Getopt::Std;
use File::Basename;
use File::Path;
use acfslib;
use osds_acfsregistrymount;

my ($STATE_FILE_NAME);    # file name used by xxx_state_file() functions
my ($ORACLE_SID) = $ENV{ORACLE_SID};

use constant CHECK_PROGRESS_NAME => "acfsregistry";

sub main
{
  my ($shell_command);    # full user entered command name
  my ($sub_command);      # start, stop, check
  my ($return_code) = 0;  # as the name implies 
  my ($diskgroup);        # diskgroup name 
  my ($volume);           # volume name 
  my ($asmadmin_user);    # ASMADMIN user name 
  my (%opt);              # user options hash - used by getopts()
  my (%flags) = ( start      => 'd:v:hs',
                  check      => 'd:v:hs',
                  stop       => 'd:v:hs',
                  clean      => 'd:v:hs',
                  enable_vol => 'd:v:hs',
                );

  # command is what the user actually typed in (sans directories).
  $COMMAND = shift(@ARGV);
  $sub_command = shift(@ARGV);

  $shell_command = dirname($0) . "/$COMMAND";
  # the .pl file lives in $ORACLE_HOME/lib but the shell front end is in bin.
  $shell_command =~ s/\/lib\//\/bin\//;

  if (defined($sub_command))
  {
    if ($sub_command eq "-h")
    {
      usage(USM_SUCCESS);
      # does not return
    }
    elsif (($sub_command eq 'start') || ($sub_command eq 'stop') ||
        ($sub_command eq 'clean') || ($sub_command eq 'check') ||
        ($sub_command eq 'enable_vol'))
    {

      # get options
      %opt=();
      getopts($flags{$sub_command}, \%opt) or usage(USM_FAIL);
      if ($opt{'d'})
      {
        # diskgroup to which to mount or unmount a volume - default, all dgs
        $diskgroup = $opt{'d'};
      }
      if ($opt{'v'})
      {
        # volume to which to mount of unmount - default, all volumes
        $volume = $opt{'v'};
      }
      if ($opt{'s'})
      {
        # silently log errors
        $SILENT = 1;
      }
      if ($opt{'h'})
      {
        # help
        usage(USM_SUCCESS);
        # does not return
      }
    }
    else
    {
      # Invalid sub command specified
      #If we are here. This is an invalid option.
      #Removing leading "-" in $sub_command. I'm getting the following error:
      # (Bad argc for usm:acfs-532)
      $sub_command =~ s/^[-]+//;
      if (!($sub_command eq "help"))
      {
        lib_error_print(532, "invalid option: %s", $sub_command);
      }      
      usage(USM_FAIL);
      exit(CLSAGFW_AE_FAIL);
    }
  }
  else
  {
    # no sub command specified
    usage(USM_FAIL);
    exit(CLSAGFW_AE_FAIL);
  }

  ###### command parsing complete #######

  # We check for drivers loaded because acfsutil info fs, called by
  # lib_recover_stale_mounts(), will throw a lot of errors if they're not.
  if (lib_check_drivers_loaded() == 0)
  {
    lib_error_print(9127, "Not all ADVM/ACFS drivers have been loaded.");
    exit(CLSAGFW_AE_FAIL);
  }

  # system mounts are recorded in the temporary state file
  $STATE_FILE_NAME = osds_get_state_file_name();

  # get a user name with which to perform ASM functions.
  ($asmadmin_user) = lib_get_asm_user();
  if (!defined($asmadmin_user))
  {
    # Look for any stale mountpoints and attempt to recover (unmount) them.
    my ($recovered_list) = lib_recover_stale_mounts();

    # Recovered (successfully unmounted) mounts are removed from the
    # state file so that they will appear to be new mounts to be done below.
    my ($recovered_entry);
    my (@recovered_list_array) = split(/ /, $recovered_list);
    foreach $recovered_entry (@recovered_list_array)
    {
      delete_from_state_file($recovered_entry);
    }

    lib_error_print(9250, "Unable to get the ASM administrator user name from the ASM process.");
    exit(CLSAGFW_AE_FAIL);
  }

  # ORACLE_SID must be set in the environment in order to be
  # able to connect to ASM.
  if (!defined($ORACLE_SID))
  {
    # Return so that we don't get a whole bunch of errors
    # by trying to connect to ASM later in this script.
    # Basically, the registry resource is ready.  Just cause it can't
    # talk to ASM doesn't mean that its not the end of the world for us.
    # But NO file systems will be mounted till ASM is up

    if ($sub_command eq "start")
    {
      # ignore the return code - we want state file processing and check 
      # timeouts mostly from start.
      start();
    }

    # Bug 9737641 - If ORACLE_SID is set, we will create the state file
    # during the initial processing of check.
    # However, if it is not, than we don't create a state file - as the
    # next check function that gets called is PERIODIC.
    #
    # If we didn't have the decision that the resource is always online
    # we could have start be called.  However, returning FAILED results
    # in potential timeouts during cleans as we try and contact ASM
    # that may or may not be up, remount file systems, etc.
    if ( $sub_command eq "check" )
    {
       my ($check_type) = $ENV{_ORA_CHECK_TYPE};
       if ((defined($check_type)) && ($check_type =~ /CLSAGFW_INITIAL_CHECK/))
       {
         # ignore the return code - we want state file processing and check 
         # timeouts mostly from start.
         start();
       }
    }

    # We return success because this is transient until ASM starts.
    exit CLSAGFW_AE_SUCCESS;
  }

  if ($sub_command eq 'enable_vol')
  {
    # The enable_vol sub_command is invoked only from within the check
    # function within this file!!! It must NOT be called with root privs!!!
    # ARGV is the registered volume list.
    $return_code = enable_vol($diskgroup, $volume, @ARGV);
    exit($return_code);
  }

  # the remaining commands require root access

  # verify root/admin access
  if (!lib_am_root())
  {
    lib_error_print(9130, "Root access required");
    exit(CLSAGFW_AE_FAIL);
  }

  if ($sub_command eq 'start')
  {
    if (!lib_usm_supported())
    {
      # OSD specific message generated in lib_usm_supported().
      exit CLSAGFW_AE_FAIL;
    }

    $return_code = start();
    if ($return_code == CLSAGFW_AE_SUCCESS)
    {
      # The check function (a mis-nomer) does the actual work
      $return_code = check($shell_command, $sub_command,
                                          $diskgroup, $volume, $asmadmin_user);
      if (($return_code == CLSAGFW_ONLINE) ||
          ($return_code == CLSAGFW_PARTIAL))
      {
        $return_code =  CLSAGFW_AE_SUCCESS;
      }
      else
      {
        $return_code = CLSAGFW_AE_FAIL;
      }
      exit $return_code;
    }
  }
  elsif ($sub_command eq 'stop')
  {
    $return_code = stop($diskgroup, $volume, 0);
  }
  elsif ($sub_command eq 'clean')
  {
    # 1 is "clean" to forcibly clear all open refs on a mount point
    $return_code = stop($diskgroup, $volume, 1);
  }
  elsif ($sub_command eq 'check')
  {
    $return_code = check($shell_command, $sub_command,
                                          $diskgroup, $volume, $asmadmin_user);
  }

  exit($return_code);
} # end main

# start
#
# Performs the initialization and sanity checks.
#
sub start
{
  my ($output_str);
  my ($ORACLE_HOME) = $ENV{ORACLE_HOME};

  # clear any check state that may exist
  lib_end_check_in_progress(CHECK_PROGRESS_NAME);

  # The state file records the file systems currently mounted by
  # this command. The main purpose is to prevent the check function
  # from re-mounting file systems that have been manually unmounted
  # by the admin.
  create_state_file();

  # make sure that we can talk to the registry
  open(REGISTRY, "$ACFSUTIL registry " . OPT_CHR . "V 11 2>&1 |");
  while ($output_str = <REGISTRY>)
  {
    #  Unable to initialize this utility's access to the Oracle Registry.
    if ($output_str =~ m/ACFS-00601/)
    {
      lib_error_print(9999, $output_str); # 9999 is not formatted
      close(REGISTRY);
      return (CLSAGFW_AE_FAIL);
    }
  }
  close(REGISTRY);
  

  return (CLSAGFW_AE_SUCCESS);
}

# stop
#
# loop through the entries in the mount table
# if $diskgroup is set, unmount only the devices in that diskgroup
# if $volume is set, unmount only those volumes
# If it is a DB home, ignore it, as we assume that 
# a resource will be handling that file system.
#
sub stop
{
  my ($diskgroup, $volume, $clean) = @_;
  my ($mount_point_array);            # pointer to the device/mountpoint array
  my ($entry);                        # entry in the sorted mount_list
  my ($mount_point);                  # current entry in the above array
  my ($unmounts_failed) = 0;          # track failed unmounts
  my ($num_mounts) = 0;               # track total ADVM mounts
  my ($num_unmounted) = 0;            # trace total ADVM mounts unmounted
  my ($return_code);
  my ($user);                         # User name of ASM process.  Used for
                                      # checking if ASM is active.
  my ($repeat) = 3;                   # Controls whether we repeat and continue
                                      # trying to unmount during a stop, or
                                      # if we only go thrice through cause its
                                      # a clean action.
  my ($CRS) = $ENV{_ORA_AGENT_ACTION};# defined if invoked via CRS
  my (@sorted_mount_list);            # umounts in reverse order
  my ($mount_index) = 0;              # index into the sorted_mount_list

  # Prevent further mounts
  mark_stop_in_state_file();

  # osds_get_ofs_mounts() returns a doubly dimensioned array of
  # mounted devices and their mountpoints.
  my ($DEVICE) = 0;
  my ($MOUNTPOINT) = 1;

  # the read_registry() registry output format is, (Linux) example:
  # /dev/asm/foo-124 /mnt DG FOO none all
  # (device, mnt point, diskgroup, volume, opts, nodes)
  my ($line);
  my ($index) = 0;
  my (@registry_list) = read_registry();
  while ($line = $registry_list[$index++])
  {
    my ($ocr_device, $ocr_mountpoint, $ocr_diskgroup,
        $ocr_volume, $ocr_options, $ocr_node) = split(/ /, $line); 

    if (defined($diskgroup) && (uc($diskgroup) ne $ocr_diskgroup))
    {
      # This diskgroup is not the one we're looking for
      next;
    }

    if (defined($volume) && (uc($volume) ne $ocr_volume))
    {
      # This volume is not the one we're looking for
      next;
    }
      
    my ($node_found) = 0;

    # see if we're interested in this mount
    if ($ocr_node ne "all")
    {
      my ($idx) = 0;
      my (@node_array) = split(/,/, $ocr_node);
      my ($my_node_name) = lc(`hostname`);
      chomp($my_node_name);

      while ($node_array[$idx])
      {
        if ($my_node_name eq lc($node_array[$idx]))
        {
          # my node is specified in the OCR - this is a mount of interest
          $node_found = 1;
        }
        $idx++;
      }
      if (! $node_found)
      {
        # not interested in this mount
        next;
      }
    }

    $sorted_mount_list[$mount_index]{'DEVICE'} = $ocr_device;
    $sorted_mount_list[$mount_index]{'MNT_POINT'} = $ocr_mountpoint;
    $sorted_mount_list[$mount_index++]{'OPTIONS'} = $ocr_options;
  }

  @sorted_mount_list = sort reverse_sort_by_mountpoint @sorted_mount_list;
  
  # Flag for control flood to alert.log
  my $flag_minute;
  
  # This part of the stop code is going to continue to wrap until
  # we TIMEOUT (from CRS) or until everything is successfully unmounted, unless
  # it is a clean, in which case, we only try several times with a pause
  # in between.
  while ($repeat)
  {   
      # Start from the top of the list
      $mount_index = 0;

      # Assume that no unmounts failed.
      $unmounts_failed = 0;
      ($user) = lib_get_asm_user();

      if ( ( ( !defined($user) )  && ( $clean == 0 ) ) && ($CRS) )
      {
        # The ASM instance is gone.
        # If we are in stop, and there is no ASM, 
        # than return a failure so that we can get on to the 
        # clean stage and clean up the mess of
        # left over mount points.
        # If we are in a clean call, than ignore it and start 
        # cleaning.
        return CLSAGFW_AE_FAIL;
      }

      foreach $entry (@sorted_mount_list)
      {
        my ($device) = $entry->{'DEVICE'};
        my ($mountpoint) = $entry->{'MNT_POINT'};

        # unmount - if mounted
        if (lib_is_mounted($mountpoint))
        {
          if ($clean)
          {
            # called from the clean function. we kill any open 
            # descriptors on the mount point.
            lib_mountpoint_descriptors($mountpoint, 1);
          }

          $return_code = lib_unmount($mountpoint);
          if ($return_code == USM_SUCCESS)
          {
            delete_from_state_file($device);
            lib_inform_print(9251, "Mount point '%s' unmounted.", $mountpoint);
          }
          else
          { 
            my ($refs) = lib_mountpoint_descriptors($mountpoint, 0);
            # Not sending to alert.log, we want to avoid repetitions.
            # At least one time the information is saved to the log.
            if ($flag_minute == (localtime)[1])
            {
              lib_error_print_noalert(9252,
                "The following process IDs have open references on mount point '%s':",
                $mountpoint);
              lib_error_print_noalert(9999, $refs);
              lib_error_print_noalert(9253,
                "Failed to unmount mount point '%s'. Mount point likely in use.",
                $mountpoint);
              lib_error_print_noalert(9254, "Manual intervention is required.");
              $unmounts_failed++;
           }
           else
           {
            lib_error_print(9252,
              "The following process IDs have open references on mount point '%s':", 
              $mountpoint);
            lib_error_print(9999, $refs);
            lib_error_print(9253,
              "Failed to unmount mount point '%s'. Mount point likely in use.",
              $mountpoint);
            lib_error_print(9254, "Manual intervention is required.");
            $unmounts_failed++;
            $flag_minute = (localtime)[1]; 
           }
          }
        }
      } #end foreach 


      if ($clean && $unmounts_failed)
      {
        # clean failed to unmount one or more file systems. This could be
        # because of an unkillable PID, or because of a race condition where
        # a process got started between the lib_mountpoint_descriptors()
        # (which would kill PIDs on a mount point) and the lib_unmount(). 
        # We can't do anything about the former but, for the latter, we
        # can retry the kill/unmount cycle.
        $repeat--;
        sleep 15;
        lib_inform_print(9275,
        "The clean failed to unmount the required mountpoint(s) - re-trying.");
      }

      # If no mount points failed to unmount, then stop repeating.
      # We've successfully unmounted all mount points that this
      # registry resource cares about (whether that's all of them
      # or only mount points on a certain disk group).
      if (!$unmounts_failed)
      {
        $repeat = 0;
      }

  } #end while

  $return_code = CLSAGFW_AE_SUCCESS;
  if ($unmounts_failed)
  {
    $return_code = CLSAGFW_AE_FAIL;
  }

  return $return_code;
} # end stop

# check
#
# loop through the devices in the registry
# if $diskgroup is set, mount only the volumes in that diskgroup
# if $volume is set, mount only those volumes
# If the OCR entry is node specific, see if we are that node

sub check
{
  my ($shell_command, $sub_command, $diskgroup, $volume, $asmadmin_user) = @_;
  my (@sorted_mount_list);
  my ($mount_failure_detected) = 0;
  my ($entry);
  my ($mount_index) = 0;
  my ($enable_vol_done) = 0;
  my ($return_code);
  my ($DEVICE) = 0;
  my ($MOUNTPOINT) = 1;
  my ($my_node_name) = lc(`hostname`);  # Run this once for performance.

  # As part of "recovery mode", following a crsd crash, check will be called
  # before start. If we return CLSAGFW_ONLINE in this case, start will
  # not get called.

  # If we're being called from start, we just fall through and mount the
  # file systems. We know that ASM is running.
  if ($sub_command eq "check")
  {
    my ($check_type) = $ENV{_ORA_CHECK_TYPE}; 

    if ((defined($check_type)) && ($check_type =~ /CLSAGFW_INITIAL_CHECK/))
    {
       # In bug 8740294 the crsd is shot.  After it restarts, it sends an
       # INITIAL_CHECK to all resources.  (This same check is sent before
       # the resources start during the startup phase of the stack.)
       # 
       # If we return UNKNOWN, like we used to (from the C code agent), the 
       # the result is that check is never called again on the resource
       # in some situations.  
       #
       # The intent of returning FAIL originally was to have start called
       # again.  However, in the situation where FAIL->CLEAN timedout, 
       # that is, we could not properly clean the resource,
       # the registry went offline and never came back online.
       #
       # Instead, we end the current check in progress ( in case there was 
       # one) and then return ONLINE.
       #
       # The next time CHECK is called, we will mount everything up.
       # This is safer than continuing, as it means that all resources
       # have had a chance to be pinged\restarted.
       #
       lib_end_check_in_progress(CHECK_PROGRESS_NAME);

       # Since we are returning online here, we need to also call start()
       # to initialize the check file.  Otherwise drive letters don't ever
       # get mounted properly.
       start();
       return CLSAGFW_ONLINE;
    }

    my ($in_progress_return) = lib_check_in_progress(CHECK_PROGRESS_NAME); 

    if ($in_progress_return == CHECK_IN_PROGRESS_YES)
    {
      return CLSAGFW_UNKNOWN;
    }
    elsif ($in_progress_return == CHECK_IN_PROGRESS_TIMEOUT)
    {
      if ($check_type =~ /CLSAGFW_CHECK_AFTER_CMD/)
      {
         # This is a check following a timeout having returned CLSAGFW_FAILED.
         # We should never get here since we now return CLSAGFW_ONLINE
         # when we detect a check timeout.
         return CLSAGFW_UNPLANNED_OFFLINE;
      }

      # We exceeded the check timeout so a previous check never completed.
      # We will return ONLINE so we get rechecked. With any luck the in
      # progress check will eventually finish.
      return CLSAGFW_ONLINE;
    }
    # the only other $in_progress_return possible is CHECK_IN_PROGRESS_NO.
  }

  # Look for any stale mountpoints and attempt to recover (unmount) them.
  my ($recovered_list) = lib_recover_stale_mounts();

  # Recovered (successfully unmounted) mounts are removed from the
  # state file so that they will appear to be new mounts to be done below. 
  my ($recovered_entry);
  my (@recovered_list_array) = split(/ /, $recovered_list);
  foreach $recovered_entry (@recovered_list_array)
  {
    delete_from_state_file($recovered_entry);
  }


  # the read_registry() registry output format is, (Linux) example:
  # /dev/asm/foo-124 /mnt DG FOO none all
  # (device, mnt point, diskgroup, volume, opts, nodes)
  my ($line);
  my ($index) = 0;
  my (@registry_list) = read_registry();
  while ($line = $registry_list[$index++])
  {
    my ($ocr_device, $ocr_mountpoint, $ocr_diskgroup,
        $ocr_volume, $ocr_options, $ocr_node) = split(/ /, $line); 

    if (defined($diskgroup) && (uc($diskgroup) ne $ocr_diskgroup))
    {
      # This diskgroup is not the one we're looking for
      next;
    }

    if (defined($volume) && (uc($volume) ne $ocr_volume))
    {
      # This volume is not the one we're looking for
      next;
    }
      
    my ($node_found) = 0;

    # see if we're interested in this mount
    if ($ocr_node ne "all")
    {
      my ($idx) = 0;
      my (@node_array) = split(/,/, $ocr_node);
      chomp($my_node_name);

      while ($node_array[$idx])
      {
        if ($my_node_name eq lc($node_array[$idx]))
        {
          # my node is specified in the OCR - this is a mount of interest
          $node_found = 1;
        }
        $idx++;
      }
      if (! $node_found)
      {
        # not interested in this mount
        next;
      }
    }

    # If the mount point has been recorded in the state file,
    # we already mounted it. Do not attempt to mount it again.
    # This is so that if a user manually dismounts a volume,
    # we won't over-ride his action.
    if (search_in_state_file($ocr_device) == USM_SUCCESS)
    {
      next;
    }

    $sorted_mount_list[$mount_index]{'DEVICE'} = $ocr_device;
    $sorted_mount_list[$mount_index]{'MNT_POINT'} = $ocr_mountpoint;
    $sorted_mount_list[$mount_index++]{'OPTIONS'} = $ocr_options;
  }

  @sorted_mount_list = sort forward_sort_by_mountpoint @sorted_mount_list;

  if (is_stop_in_state_file() == USM_SUCCESS)
  {
    # The stop function has been called.
    #
    # After a CRS resource is successfully stopped, the check function
    # is called. CRS expects to see a fail to verify that the resource
    # is no longer active.
    my ($mount_point);

    $return_code = CLSAGFW_PLANNED_OFFLINE;
    foreach $mount_point (@sorted_mount_list)
    {
      if ( lib_is_mounted($mount_point->{'MNT_POINT'}) )
      {
        # We found a mount point that was mounted.
        lib_end_check_in_progress(CHECK_PROGRESS_NAME);
        return CLSAGFW_FAILED;
      }
    }

    # We used to remove the state file 'stop' here.
    # In bug 9855439, we got a stop->check->check.
    # This meant that we were remounting everything again, 
    #   since there was no stop in the state file, and 
    #   that's what check does.
    # This is okay to do, because 'start' is always called
    #   even during a 'check', so we always recreate the file.
    lib_end_check_in_progress(CHECK_PROGRESS_NAME);
    return $return_code;
  }

  foreach $entry (@sorted_mount_list)
  {
    my ($ocr_device) = $entry->{'DEVICE'};
    my ($ocr_mountpoint) = $entry->{'MNT_POINT'};
    my ($ocr_options) = $entry->{'OPTIONS'};

    # double check that stop has not run since the last test
    if (is_stop_in_state_file() == USM_SUCCESS)
    {
      # There is a race condition where a stop ran in the middle of a check.
      #
      # We call stop internally to unmount everything that may have been
      # mounted in this loop before the stop was detected.
      my ($cmd) = "$shell_command stop";
      if (defined($diskgroup))
      {
        $cmd .= " -d $diskgroup";
      }
      if (defined($volume))
      {
        $cmd .= " -v $volume";
      }
      if ($SILENT)
      {
        $cmd .= " -s";
      }
      # TODO: I don't know if this will work on windows.
      # There has been an issue with command recursion.
      $return_code = lib_run_as_user("root", $cmd);
      last;
    }

    if (!lib_is_mounted($ocr_mountpoint))
    {
      if (! -d "$ocr_mountpoint")
      {
        if ( lib_create_mount_point ( $ocr_mountpoint ) != USM_SUCCESS )
        {
          return CLSAGFW_AE_FAIL;
        }
      }

      if (! $enable_vol_done)
      {
        my ($add);

        # If needed, ASM mount all required diskgroups,
        # enable all required volumes
        my ($cmd) = "$shell_command enable_vol";
        if (defined($diskgroup))
        {
          $cmd .= " -d $diskgroup";
        }
        if (defined($volume))
        {
          $cmd .= " -v $volume";
        }
        if ($SILENT)
        {
          $cmd .= " -s";
        }
       
        my ($list);
        # add the registry_list for enable_vol processing.
        foreach $add (@registry_list)
        {
          $list .= " $add";
        }
        $cmd .= " $list";

        if ( $^O =~ /Win.*/ )
        {
          # On Windows, in the CRS env, having an extra space in front of the 
          # list pushes all the other arguments off by one, so the 
          # volume enablement fails.
          $list =~ s/\s+/ /g;   # condense space separation to single space
          $return_code = enable_vol($diskgroup, $volume, split(/ /, trim($list)));
        }
        else
        {
          $ENV{_ORA_VOLUME_DEVICE} = $ocr_device;
          $return_code = lib_run_as_user($asmadmin_user, $cmd);
        }
        if ($return_code == USM_SUCCESS)
        {
          $enable_vol_done = 1;
        }
      }
 
  # TODO: go over this with Tony.  Should we skip trying to mount if
  # we can't enable the volume?  I'm not clear how this set of 
  # code should behave, nor if we should just return a better error
  #
  
      # the mount point is not active, we can attempt the mount
      lib_inform_print(9257,
                     "Mounting device '%s' on mount point '%s'.",
                     $ocr_device, $ocr_mountpoint);
      $return_code = lib_mount($ocr_device, $ocr_mountpoint, $ocr_options);
      if ($return_code == USM_SUCCESS)
      {
        add_to_state_file($ocr_device);
      }
      else
      {
        lib_error_print(9258,
                "Failed to mount device '%s' on mount point '%s'.", 
                $ocr_device, $ocr_mountpoint);
        $mount_failure_detected = 1;
        next;
      }
    }
    else
    {
      # We may see this if an OCR entry is also a DB home and the DB 
      # home got mounted first.
      #
      # We still add it to the state file so that we won't try to re-mount
      # it should the user do a manual unmount
      lib_inform_print(9259, "Mount point '%s' already has a device " .
                             "mounted on it.", $ocr_mountpoint);
      add_to_state_file($ocr_device);
    }
  } #end while 

  $return_code = CLSAGFW_ONLINE;
  lib_end_check_in_progress(CHECK_PROGRESS_NAME);

# TODO
# This should work but if a file system fails to mount during start,
# resource monitoring stops if CLSAGFW_PARTIAL gets returned.
# So, we return CLSAGFW_ONLINE because the resource is online.
# if ($mount_failure_detected)
# {
#   $return_code = CLSAGFW_PARTIAL;
# }
  return $return_code;
} # end check

# Called in USER context. If required, the function will ASM mount the
# required diskgroup and enable the required volume.
#
sub enable_vol
{
  my ($dg, $vol, @registry_list) = @_;
  my ($return_code) = USM_SUCCESS;
  my ($dbh);                                  # SQL database handle

  # Registry contents is passed by the caller - (Linux) example:
  # /dev/asm/foo-124 /mnt DG FOO none all
  # (device, mnt point, diskgroup, volume, opts, nodes)
  while (@registry_list)
  {
    my ($ocr_device) = shift(@registry_list);
    my ($ocr_mountpoint) = shift(@registry_list);
    my ($ocr_diskgroup) = shift(@registry_list);
    my ($ocr_volume) = shift(@registry_list);
    my ($ocr_options) = shift(@registry_list);
    my ($ocr_node) = shift(@registry_list);

    if (defined($dg) && (uc($dg) ne $ocr_diskgroup))
    {
      # not interested in this disk group
      next;
    }

    if (defined($vol) && (uc($vol) ne $ocr_volume))
    {
      # not interested in this volume
      next;
    }
      
    my ($node_found) = 0;
 
    # see if we're interested in this mount
    if ($ocr_node ne "all")
    {
      my ($idx) = 0;
      my (@node_array) = split(/,/, $ocr_node);
      my ($my_node_name) = lc(`hostname`);
      chomp($my_node_name);
 
      while ($node_array[$idx])
      {
        if ($my_node_name eq lc($node_array[$idx]))
        {
          # my node is specified in the OCR - this is a mount of interest
          $node_found = 1;
        }
        $idx++;
      }
      if (! $node_found)
      {
        # not interested in this mount
        next;
      }
    } 

    if (search_in_state_file($ocr_device) == USM_SUCCESS)
    {
      # The device is in the state file and so we have already mounted the dg
      # and enabled the volume. At this point, the volume is up and running
      # or the mount is stale and unroverable. In either case, we're done.

      next;
    }

    if (!defined($dbh))
    {
      # connect to the ASM instance
      $dbh = lib_asm_connect();
      if ($dbh == USM_FAIL)
      {
        lib_error_print(9260, "Failed to connect to the ASM instance.");
        return USM_FAIL;
      }
    }

    $return_code = lib_asm_mount_diskgroup($dbh, $ocr_diskgroup);
    if ($return_code == USM_FAIL)
    {
      # error message generated in lib_asm_mount_diskgroup()
      return USM_FAIL;
    }

    $return_code = lib_asm_enable_volume($dbh, $ocr_diskgroup, $ocr_volume);
    if ($return_code == USM_FAIL)
    {
      # error message generated in asm_lib_asm_enable_volume()
      return USM_FAIL;
    }
  } #end while

  lib_asm_disconnect($dbh);

  return($return_code);
}
# end enable_vol

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

sub usage
{
  my ($exit) = @_;

  lib_error_print(9134, "usage:");
  lib_error_print(9265,
              "  {start|stop|clean|check} [-d diskgroup] [-v volume] [-h] [-s]");
  lib_error_print(9131,
                    "        [-s]             - silent mode (error messages only)");
  lib_error_print(9132, "        [-h]             - print help/usage information");
  exit($exit);
}

# read_registry
#
# An acfsutil registry mount entry consists of 7 lines:
# Mount Object:
#   Device: /dev/asm/foo-420
#   Mount Point: /mnt
#   Disk Group: DG
#   Volume: FOO
#   Options: none
#   Nodes: all
#
# In order to avoid dependence on language translations, we look for the
# last field of lines 1-7.
#
sub read_registry
{
  my (@registry_info);
  my ($entry_num);
  my ($index) = 0;

  open REGISTRY, "$ACFSUTIL registry " . OPT_CHR . "V 11|" or warn "$!";
  $entry_num = -1;
  while (<REGISTRY>)
  {
    # get the last field in the acfsutil registry line
    my (@array) = split / /, $_;
    my ($value) = $array[$#array];
    chomp($value);

    $entry_num++;
    if ($entry_num == 0)
    {
      # Mount Object:
      next;
    }

    $registry_info[$index] .= "$value ";
    
    if ($entry_num == 6)
    {
      # look for next mount in the registry
      $entry_num = -1;
      $index++;
    }
  }
  close (REGISTRY);

  return @registry_info;
} # end read_registry

# forward_sort_by_mountpoint
#
# Sort registry entries by mount point.
#
sub forward_sort_by_mountpoint
{
    $a->{'MNT_POINT'} cmp $b->{'MNT_POINT'}
}

# reverse_sort_by_mountpoint
#
# Sort registry entries by mount point.
#
sub reverse_sort_by_mountpoint
{
    $b->{'MNT_POINT'} cmp $a->{'MNT_POINT'}
}

#
# state file ops
#
# Currently, the file is a simple text file. If we discover that users
# have gobs and gobs of mounted USM file systems, we'll want to make this
# a binary file consisting of a hash table... or something like that.
#
# Although no state file contention is expected (but we never know :-)),
# Locking the file exclusively (LOCK_EX) for writes forces the data to
# be flushed. The file close drops the lock.
#

use Fcntl qw/:flock/;
use constant USM_STOP => 2;

# Create state file - over-write existing, if any.
#
sub create_state_file
{
  open OUT, "+>$STATE_FILE_NAME"
                    or die "can't open $STATE_FILE_NAME for create: $!";
  close(OUT);
  return (USM_SUCCESS);
}

# Add a unique entry (device) to the file.
# Fail if a stop has been recorded.
#
sub add_to_state_file
{
  my($device) = @_;
  my($ret);
  open OUT, ">>$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";
  flock(OUT, LOCK_EX);

  $ret = search_in_state_file_locked($device);

  if ($ret == USM_SUCCESS)
  {
    # lib_inform_print(9999, "Entry $device already exists.");
    $ret = USM_FAIL;
  }
  elsif ($ret == USM_STOP)
  {
    # lib_inform_print(9999, "Stop detected.");
    $ret = USM_FAIL;
  }
  else
  {
    print OUT "$device\n";
    $ret = USM_SUCCESS;
  }
  close(OUT);
  return($ret);
}

# Search for an entry (device) in the state file.
 
# search_in_state_file
#   called with state file unlocked - from check() and enable_vol().
#
sub search_in_state_file
{
  my ($device) = @_;
  return search_in_state_common($device, 0);
}

# search_in_state_file_locked
#   called locked from within another *_state_file rountine.
#
sub search_in_state_file_locked
{
  my ($device) = @_;
  return search_in_state_common($device, 1);
}

sub search_in_state_common
{
  my($device, $locked) = @_;
  my ($entry);
  open IN, "<$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";

  if (! $locked)
  {
    flock(IN, LOCK_EX);
  }

  while($entry = <IN>)
  {
    chomp($entry);
    my (@array) = split(/ /, $entry);
    if ($array[0] eq $device)
    {
      close(IN);
      return(USM_SUCCESS);
    }
    elsif ($array[0] eq "stop")
    {
      # lib_inform_print(9999, "Stop detected.");
      close(IN);
      return(USM_STOP);
    }
  }
  close(IN);
  return(USM_FAIL);
}

# Indicate that the stop function has been called.
#
sub mark_stop_in_state_file
{
  my($ret);
  open OUT, ">$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";
  flock(OUT, LOCK_EX);
  print OUT "stop\n";
  close(OUT);
  return(USM_SUCCESS);
}

# See if mark_stop_in_state_file() has been called
#
sub is_stop_in_state_file
{
  open IN, "<$STATE_FILE_NAME" or return USM_FAIL;
  flock(IN, LOCK_EX);
  
  my $entry = <IN>;
  if (defined($entry) && ($entry eq "stop\n"))
  {
    close(IN);
    return USM_SUCCESS;
  }

  close(IN);
  return USM_FAIL;  
}

# Delete an existing entry (mountpoint) from the file.
# Return fail if a stop has been recorded.
#
sub delete_from_state_file
{
  my($device) = @_;
  my ($entry);
  my($ret);
  open IN, "<$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";
  flock(IN, LOCK_EX);

  $ret = search_in_state_file_locked($device);
  close(IN);

  if ($ret == USM_FAIL)
  {
     # lib_inform_print(9999, "Entry $device not in file.");
     $ret = USM_FAIL;
  }
  elsif ($ret == USM_STOP)
  {
     # lib_inform_print(9999, "Stop recorded.");
     $ret = USM_FAIL;
  }
  else
  {
    my ($idx) = 0;
    my(@file);
    open IN, "<$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";
    flock(IN, LOCK_EX);

    while($entry = <IN>)
    {
      chomp($entry);
      if ($entry eq $device)
      {
        next;
      }

      $file[$idx++] = $entry;
    }

    open OUT, ">$STATE_FILE_NAME" or die "can't open $STATE_FILE_NAME: $!";
    $idx = 0;
    while($file[$idx])
    {
      print OUT "$file[$idx++]\n";
    }

    close (IN);
    close (OUT);
    
    $ret = USM_SUCCESS;
  }
  return($ret);
}

# Delete the state file.
#
sub delete_state_file
{
  unlink($STATE_FILE_NAME);
  return(USM_SUCCESS);
}

main();

# Subroutine to send commands to diskpart

sub send_diskpart_command
{

  my $available = 1;
  my ($diskpart_command, $drive_letter) = @_;

  #DiskPart requires a textfile input. Delete it if already exists.
  my $file = "diskpart_cmd.txt";
  unlink($file);

  open (DPART, ">>$file") || die ("Could not open file.$!");
  print DPART ($diskpart_command);
  close (DPART);

  #Sending $diskpart_command
  open (FH, "diskpart /s diskpart_cmd.txt|") || die ("Could not open file.$!");

  my @inFile=<FH>; #read all at once into an array

  foreach (@inFile)
  {
    if (/\s$drive_letter\s/) #check if a drive letter is available
    {
      $available = 0;
    }
  }

  close (FH);

  #delete the text file
  unlink($file);

  return $available;


}
