# 
#
# acfsload.pl
# 
# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      acfsload.pl
#
#    DESCRIPTION
#      load/unload USM kernel drivers
#
#    NOTES
#      acfsload start
#          load USM kernel drivers. If the drivers are already loaded,
#          no action is taken.
#      acfsload stop 
#          unload USM kernel drivers
#      acfsload check
#          basic verification that USM drivers are up.
#
#      This command is expected to be called from CRS when clusterware is 
#      started and the USM drivers loaded. we make no explicit checks here.
#
#      User output should be done in this file when possible to ensure
#      cross platform similarity in command look and feel. However, it is 
#      understood that some OSD actions are specific to one platform only,
#      in which case it makes sense to do the output there.
#
# 

use strict;
use Getopt::Std;
use Cwd 'abs_path';
use acfslib;
use osds_acfsload;
use IO::Handle;

# acfsutil command line option switch.
use Config;
my ($is_check) = 0;                   # 'check' sub commands do not get logged
my ($optc) = '-';                     # use a '-' or '/' for acfsutil switches
$optc = '/' if ($Config{osname} =~ /Win/);

sub main
{
  my ($sub_command);      # start or stop
  my ($return_code);      # as the name implies 
  # user options
  my (%opt);                 # user options hash - used by getopts()
  # user options.
  my (%flags) = ( start => 's',
                  stop  => 's',
                  clean => 's',
                  check => 's',
                );

  # supplied by the front end driver and 'guaranteed' to be there.
  # command is what the user actually typed in (sans directories).
  $COMMAND = shift(@ARGV);

  # supplied by user
  $sub_command = shift(@ARGV);

  # We don't log the check sub_command because it would fill the log with
  # mostly useless information.
  if ((defined($sub_command)) && ($sub_command eq "check"))
  {
    $is_check = 1;
  }

  if (!defined($sub_command))
  {
    # does not return
    usage("", CLSAGFW_AE_FAIL);
  }

  if ($sub_command eq "-h")
  {
    usage("", USM_SUCCESS);
  }
  elsif (!(($sub_command eq "start") ||  ($sub_command eq "clean") ||
       ($sub_command eq "stop") || ($sub_command eq "check")))
  {
    #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);
    }
    # does not return
    usage("", CLSAGFW_AE_FAIL);
  }

  # parse user options
  %opt=();
  getopts($flags{$sub_command}, \%opt) or usage($sub_command, CLSAGFW_AE_FAIL);
  if ($opt{'s'})
  {
    # do not generate console output - default is not silent.
    $SILENT = 1;
  }

  # command parsing complete

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

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

    # load the USM drivers
    $return_code = start();
  }
  elsif ($sub_command eq 'stop')
  {
    # unload the USM drivers
    $return_code = stop($sub_command);
  }
  elsif ($sub_command eq 'clean')
  {
    $return_code = clean($sub_command);
  }
  else
  {
    # check the USM drivers
    $return_code = check();
  }

  acfsload_exit($return_code);
}

# If the drivers are already loaded, no action is taken
#
sub start
{
  my ($driver);
  my ($return_code);

  lib_inform_print(9391, "Checking for existing ADVM/ACFS installation." );

  # No drivers to load if they are not installed on the system
  if (!lib_check_drivers_installed())
  {
    lib_error_print(9129, "ADVM/ACFS not installed");
    return CLSAGFW_AE_FAIL;
  }

  lib_inform_print(9392, "Validating ADVM/ACFS installation files " .
		   "for operating system.");

  # Make sure that the  drivers are correct for this operating system
  if (!osds_verify_correct_driver_version())
  {
    lib_error_print(9226,
                "ADVM/ACFS drivers not correct for this OS - cannot load.");
    return CLSAGFW_AE_FAIL;
  }
  
  lib_inform_print(9393, "Verifying ASM Administrator setup.");

  # if the asmadmin name has changed, re-create the udev entries
  osds_check_asmadmin_changed();

  lib_inform_print(9308, "Loading installed ADVM/ACFS drivers.");
  

  $return_code = lib_load_usm_drivers();

  if ($return_code)
  {
    lib_error_print(9127, "Not all ADVM/ACFS drivers have been loaded.");
    return CLSAGFW_AE_FAIL;
  }

  # make sure that the USM drivers have created the required infrastructure. 
  lib_inform_print(9327, "Verifying ADVM/ACFS devices.");
  $return_code = lib_verify_usm_devices();
  if ($return_code == USM_FAIL)
  {
    # osds_verify_usm_devices() will print the specific error(s);
    return CLSAGFW_AE_FAIL;
  }


  lib_inform_print(9322, "completed");

  return CLSAGFW_AE_SUCCESS;
}

# Here we really get nasty.  We'll kill everything we can to get 
# our mount points down and drivers unloaded.
sub clean
{
  my ($sub_command) = @_;
  # osds_get_ofs_mounts() returns a doubly dimensioned array of
  # mounted devices and their mountpoints.
  my ($DEVICE) = 0;
  my ($MOUNTPOINT) = 1;
  my ($user) = lib_get_asm_user();  # Used to check if ASM is still active.
  my ($unmounts_failed) = 0;
  my ($mount_point_array);            # pointer to the device/mountpoint array
  my (@sorted_mount_list);            # umounts in reverse order
  my ($entry);                        # entry in the sorted mount_list
  my ($return_code) = 0;
  my ($mount_point);                  # current entry in the above array
  my ($mount_index) = 0;              # index into the sorted_mount_list

  #  For 11.2.0.3 use this global variable to indicate that we
  #  are cleaning.  The beautification team will clean this up in 12c.
  $acfslib::CRS_ACTION = "clean";

  $return_code = lib_count_drivers_loaded();
  
  if ($return_code eq 0)  # drivers not loaded so nothing to clean
  {
    return CLSAGFW_AE_SUCCESS;
  }

  $mount_point_array = lib_get_advm_mounts();
  foreach $mount_point (@$mount_point_array)
  {
    my ($device) = $mount_point->[$DEVICE];
    my ($mountpoint) = $mount_point->[$MOUNTPOINT];
    my ($cur_dg);

    $sorted_mount_list[$mount_index]{'DEVICE'} = $device;
    $sorted_mount_list[$mount_index++]{'MNT_POINT'} = $mountpoint;
  }

  @sorted_mount_list = sort reverse_sort_by_mountpoint @sorted_mount_list;

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

    # unmount - if mounted
    if (lib_is_mounted($mountpoint))
    {
      lib_mountpoint_descriptors($mountpoint, 1);
      $return_code = lib_unmount($mountpoint);

      if ($return_code == USM_SUCCESS)
      {
        lib_inform_print(9251, "Mount point '%s' unmounted.", $mountpoint);
      }
      else
      {
         my ($refs) = lib_mountpoint_descriptors($mountpoint, 0);
         lib_inform_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++;
      }
    }
  }
  # This will sleep until ASM goes away.  Without ASM going away,
  # we can't unload the drivers cleanly,
  # as they hold references open on us.
  while ( (defined($user)) && ($unmounts_failed == 0) )
  {
    sleep(10);
    ($user) = lib_get_asm_user();
  }
  
  my ($return_val) = lib_unload_usm_drivers(undef, $sub_command);
  if ($unmounts_failed)
  { 
    # If we have file systems still open, than the drivers didn't
    # unload, so set return_val to fail no matter what.
    $return_val = USM_FAIL;
  }
  return (($return_val == USM_SUCCESS) ? CLSAGFW_AE_SUCCESS : CLSAGFW_AE_FAIL);
}

# The most common reason for the failure to unload a driver is if
# some component is currently using it
#
sub stop
{
  my ($sub_command) = @_;
  my ($CRS) = $ENV{_ORA_AGENT_ACTION};  # defined if invoked via CRS
  my ($user) = lib_get_asm_user();      # defined if ASM is running
  my ($timeoutMax) = 1200;
  my ($timeout) = 0;
  
  # This will sleep until ASM goes away.  Without ASM going away,
  # we can't unload the drivers cleanly, as they hold references open on us.
  #
  # If called from the command line, don't wait for ASM to stop, attempt the
  # unload anyway. If the drivers are in use, the user will get a message.
  while (defined($user) && defined($CRS) && ($timeout < $timeoutMax))
  {
    lib_inform_print(9290, "Waiting for ASM to shutdown.");
    sleep(10);
    $timeout += 10;
    ($user) = lib_get_asm_user();
  }
  return CLSAGFW_AE_FAIL if ($timeout >= $timeoutMax);
  
  my ($return_val) = lib_unload_usm_drivers(undef, $sub_command);
  return (($return_val == USM_SUCCESS) ? CLSAGFW_AE_SUCCESS : CLSAGFW_AE_FAIL);
}


# check
#
# If we can talk to the control devices, we assume that the drivers
# are loaded and the resource is online.
#
sub check
{
  my ($ret) = CLSAGFW_UNPLANNED_OFFLINE;

  if (lib_control_devices_accessible())
  {
    $ret = CLSAGFW_ONLINE;
  }

  return $ret;
} 

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

  lib_error_print_noalert(9228, "usage: %s [-h] {start|stop} [-s]", $COMMAND);
  acfsload_exit($exit);
}

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

# acfsload_exit does not return
#
sub acfsload_exit
{
  my ($ret) = @_;
  exit $ret;
}

main();

