# 
#
# acfssinglefsmount.pl
# 
# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. 
#
#    NAME
#      acfssinglefsmount.pl
#
#    DESCRIPTION
#      Mount a specified USM file system. Unmount USM file system.
#      Normally called by the ora.<dg>.<vol>.acfs CRS agent.
#
#    USAGE
#      Normal usage is for ora.<dg>.<vol>.acfs to pass arguments via the env.
#      For testing, the arguments may be specified on the command line instead.
#
#      acfssinglefsmount start [-d device -g diskgroup -v volume -m mountpoint]
#          - the volume may not be ASM enabled so we need to check and
#            do it here if needed.
#          - mount file system
#      acfssinglefsmount stop [-m mount_point]
#          unmount USM file system
#      acfssinglefsmount check [-m mount_point]
#          verify that the volume is mounted
#      Additionally, all commands above accept [-s] (silent) [-h] (help)
#
#    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, the USM drivers loaded.
#
#      This command is called as root but things like ASM volume enables 
#      have to be done by a user with asmadmin privs.
#      This is an ASM bug (6900692) 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 DBI;
use File::Basename;
use File::Path;
use acfslib;
use osds_acfssinglefsmount;



my ($ORACLE_SID) = $ENV{ORACLE_SID};
my ($DEBUG) = $ENV{SRCHOME};

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 ($mount_point);      # mount point name 
  my ($diskgroup);        # disk group name 
  my ($volume);           # volume name 
  my ($advm_device);      # advm_device
  my ($ora_name);         # emvironment name containing diskgroup and volume
  my (%opt);              # user options hash - used by getopts()
  my (%flags) = ( start       => 'd:g:m:hs',
                  stop        => 'm:hs',
                  check       => 'm:hs',
                  clean       => 'm:hs',
                  enable_vol  => 'd:g:v:n:hs',
                  disable_vol => 'd:g:v:n: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\//;

  # ora.<dg.<vol>.acfs passes arguments via the environment
  # For testing, we also allow command line args that over-ride the env.
  $advm_device = $ENV{_ORA_VOLUME_DEVICE};
  $mount_point = $ENV{_ORA_MOUNTPOINT_PATH};
  $ora_name = $ENV{_ORA_NAME};

  # This script is being used to enable volumes On AIX in 12.1.  Setting
  # the environmental variables from the agent isn't working (setpenv
  # problems in scls_process_spawn).  The agent will pass the
  # volume resource name and device path on the command line.
  if ( defined($sub_command) &&
       ( $sub_command eq "enable_vol" || 
         $sub_command eq "disable_vol" ) )
  {
    %opt=();
    getopts($flags{$sub_command}, \%opt) or usage(CLSAGFW_AE_FAIL);
    if ($opt{'n'})
    {
      # device name
      $ora_name = $opt{'n'};
    }
    if ($opt{'d'})
    {
      # device name
      $advm_device = $opt{'d'};
      if ( ! defined ( $ENV{_ORA_VOLUME_DEVICE} ) )
      {
        $ENV{_ORA_VOLUME_DEVICE} = $advm_device;
      }
    }
  }

  if (defined($ora_name))
  {
    # The _ORA_NAME format is ora.<diskgroup>.<volume>.asmfs 
    my ($ora, $dg, $vol, $asmfs) = split(/\./, $ora_name);

    if ($ora ne "ora")
    {
      lib_error_print (9261, "Invalid _ORA_NAME format.");
      usage(CLSAGFW_AE_FAIL);
      exit CLSAGFW_AE_FAIL;
    }

    $diskgroup = $dg;
    $volume = $vol;
  }

  if (defined($sub_command))
  {
    if ($sub_command eq "-h")
    {
      usage(USM_SUCCESS);   
    }    
    elsif (!(($sub_command eq "start") || ($sub_command eq "stop") ||
        ($sub_command eq "check") || ($sub_command eq "clean") ||
        ($sub_command eq "enable_vol") || ($sub_command eq "disable_vol") ))
    {
      if (($sub_command eq "-help") ||
          ($sub_command eq "--help"))
      {
        usage(CLSAGFW_AE_FAIL);
      }
      else 
      {
        lib_error_print(9262, "Invalid sub-command '%s'.", $sub_command);
        lib_error_print(9263,
               "  Valid sub-commands are 'start', 'stop', 'check', and 'clean'.");
        exit CLSAGFW_AE_FAIL;
      }
    }
 
    # get options
    # Normally we get the arguments passed in through the environment
    # by the agent. but, for testing, we'll permit command line switches.
    %opt=();
    getopts($flags{$sub_command}, \%opt) or usage(CLSAGFW_AE_FAIL);
    if (defined($DEBUG))
    {
      # development environment
      if ($opt{'d'})
      {
        # device name
        $advm_device = $opt{'d'};
      }
      if ($opt{'g'})
      {
        # diskgroup name
        $diskgroup = $opt{'g'};
      }
      if ($opt{'m'})
      {
        # mount point name
        $mount_point = $opt{'m'};
      }
    } # end DEBUG
    if ($opt{'s'})
    {
      # Do not print informational messages - only print errors
      $SILENT = 1;
    }
    if ($opt{'h'})
    {
      # help
      usage(USM_SUCCESS);
      # does not return
    }
  }
  else
  {
    # no sub command specified
    usage(CLSAGFW_AE_FAIL);
    exit(CLSAGFW_AE_FAIL);
  }

  # debug only - production code will have gotten all args from the
  # environment and will have failed before here if $volume not set.
  if ((defined($DEBUG)) && (!defined($volume)) &&
      ( ($sub_command eq "start") ||
        ($sub_command eq "enable_vol") ||
        ($sub_command eq "disable_vol") ))
  {
    if (!defined($advm_device))
    {
      usage(CLSAGFW_AE_FAIL);
      exit(CLSAGFW_AE_FAIL);
    }

    # get the volume from the device
    my (@array) = split(/\//, $advm_device);
    $volume = $array[$#array];
    @array = split(/-/, $volume);
    $volume = $array[$#array-1];
  }

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

  # ORACLE_SID must be set in the environment in order to be
  # able to connect to ASM.
  if (!defined($ORACLE_SID))
  {
    lib_error_print(9289, "%s not set in environment.", "ORACLE_SID");
    exit CLSAGFW_AE_FAIL;
  }

  if ($sub_command eq 'enable_vol')
  {
    # The enable_vol sub_command is invoked only from within the start
    # function within this file!!! It must NOT be called with root privs!!!
    $return_code = enable_vol($diskgroup, $volume);
    exit($return_code);
  }
  elsif ($sub_command eq 'disable_vol')
  {
    $return_code = disable_vol($diskgroup, $volume);
    exit($return_code);
  }
  elsif ($sub_command eq 'check')
  {
    $return_code = check($mount_point);
    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(
             $shell_command, $advm_device, $diskgroup, $volume, $mount_point);
  }
  elsif ($sub_command eq 'stop')
  {
    $return_code = stop($mount_point, 0);
  }
  elsif ($sub_command eq 'clean')
  {
    $return_code = stop($mount_point, 1);
  }

  exit($return_code);
} # end main

# start
#
# Performs the initialization and sanity checks.
#
sub start
{
  my ($shell_command, $advm_device, $diskgroup, $volume, $mount_point) = @_;
  my ($mount_options);
  my ($asmadmin_user);    # ASMADMIN user name 
  my ($return_code) = CLSAGFW_AE_SUCCESS;
  my ($err_str);

  if ((!defined($advm_device)) ||
      (!defined($diskgroup)) ||
      (!defined($volume)) ||
      (!defined($mount_point)))
  {
    usage(CLSAGFW_AE_FAIL);
    return CLSAGFW_AE_FAIL;
  }

  # Verify that this is not a relative path.  We do not support relative
  # paths.

  if ( ! lib_is_abs_path ( $mount_point ) )
  {
      acfslib::lib_error_print(9366,
          "Relative path for mount point '%s' is not supported.", $mount_point);
      return CLSAGFW_AE_FAIL;
  }

  # platform dependent initialization
  $err_str = osds_initialize();
  if (defined($err_str))
  {
    lib_error_print(9999, "$err_str");   # 9999 is not formatted
    return CLSAGFW_AE_FAIL;
  }

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

  # 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);
  }
 
  # If the mountpoint is stale,  attempt to recover (unmount) it.
  lib_recover_stale_mounts($mount_point);
  if (!lib_is_mounted($mount_point))
  {
    if (! -d "$mount_point")
    {
      if ( lib_create_mount_point ( $mount_point ) != USM_SUCCESS )
      {
        return CLSAGFW_AE_FAIL;
      }
    }

    # get a user name with which to perform ASM functions in enable_vol().
    ($asmadmin_user) = lib_get_asm_user();
    if (!defined($asmadmin_user))
    {
      lib_error_print(9250, "Unable to get the ASM administrator user name from the ASM process.");
      return CLSAGFW_AE_FAIL;
    }

    # If needed, ASM mount all required diskgroups, enable all required volumes
    my ($cmd) = "$shell_command enable_vol -d $advm_device -g $diskgroup -v $volume";
    if ($SILENT)
    {
      $cmd .= " -s";
    }
    if ($^O =~ /Win.*/)
    {
      $return_code=enable_vol($diskgroup, $volume);
    }
    else
    {
      $return_code = lib_run_as_user($asmadmin_user, $cmd);
    }

    if ($return_code != USM_SUCCESS)
    {
      return CLSAGFW_AE_FAIL;
    }

    # the mount point is not active, we can attempt the mount
    lib_inform_print(9257, "Mounting device '%s' on mount point '%s'.", 
                     $advm_device, $mount_point);

    # mount_options not used for now
    $mount_options = "none";

    $return_code = lib_mount($advm_device, $mount_point, $mount_options);
    if ($return_code != USM_SUCCESS)
    {
      lib_error_print(9258,
                "Failed to mount device '%s' on mount point '%s'.", 
                $advm_device, $mount_point);

      $return_code = CLSAGFW_AE_FAIL;
    }
  }
  else
  {
    # We may see this if an OCR entry is also a DB home and the DB 
    # home got mounted first.
    lib_inform_print(9259, "Mount point '%s' already has a device mounted" .
                           " on it.", $mount_point);
    $return_code = CLSAGFW_AE_SUCCESS;
  }

  return ($return_code);
}

# stop
#
# Unmount the mount point for this single file system resource.
# 
#
sub stop
{
  my ($mount_point) = @_;
  
  if (!defined($mount_point))
  {
    usage(CLSAGFW_AE_FAIL);
  }

  lib_unmount($mount_point);

  # Return success. Checking is done by the agent
  return CLSAGFW_AE_SUCCESS;

} # end stop

# check
#
sub check
{
  my ($mount_point) = @_;

  if (!defined($mount_point))
  {
    usage(CLSAGFW_AE_FAIL);
  }

  # 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.
  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 let the check progress normally.  We check at the end
       # for the initial check if the mount point is not mounted (
       # during an initial check we don't expect it to be mounted ).
       # If the mount point is not mounted during an initial check
       # we return unplanned offline, causing CRS to clean up after it
       # by calling stop, clean, start.
       #
       # If it is not initial check, we return planned_offline so we don't
       # remount the file system on the admin.


       lib_end_check_in_progress($mount_point);
 
  }

  my ($in_progress_return) = lib_check_in_progress($mount_point);

  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.
       return CLSAGFW_UNPLANNED_OFFLINE;
    }

    # We exceeded the check timeout so a previous check never completed.
    # returning a CLSAGFW_FAILED will take the resource off line,
    # preventing further calls to check.
    return CLSAGFW_FAILED;
  }
  # the only other $in_progress_return possible is CHECK_IN_PROGRESS_NO.

  if (lib_is_mounted($mount_point))
  {
    my $avail = lib_is_mount_available($mount_point);
    if ($avail == 1)
    {   
        lib_end_check_in_progress($mount_point);
        return CLSAGFW_ONLINE;
    }
    elsif ($avail == -1)
    {
        # We were unable to run acfsutil to verify this mount point
        # Possible issues:
        # 1) This is no longer an ACFS mount point.
        # 2) This mount point has been deleted from the file system.
        # 3) acfsutil is gone from the system.
        lib_end_check_in_progress($mount_point);

        # Tell CRS we don't know what to do with this mount point anymore.
        return CLSAGFW_UNKNOWN;
    }
  }
  else
  {
    if ((defined($check_type)) && ($check_type !~ /CLSAGFW_INITIAL_CHECK/))
    {
    lib_end_check_in_progress($mount_point);
    return CLSAGFW_PLANNED_OFFLINE;
    }
  }

  lib_end_check_in_progress($mount_point);
  return CLSAGFW_UNPLANNED_OFFLINE;
}

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

  # 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, $diskgroup);
  if ($return_code == USM_FAIL)
  {
    # error message generated in lib_asm_mount_diskgroup()
    return USM_FAIL;
  }

  $return_code = lib_asm_enable_volume($dbh, $diskgroup, $volume);
  if ($return_code == USM_FAIL)
  {
    # error message generated in asm_lib_asm_enable_volume()
    return USM_FAIL;
  }
 
  lib_asm_disconnect($dbh);

  return($return_code);
}
# end enable_vol

# disable_vol
#
sub disable_vol
{
  my ($diskgroup, $volume) = @_;
  my ($return_code) = USM_SUCCESS;
  my ($volume_found) = 0;
  my ($dbh);                  # SQL database handle

  # 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_disable_volume($dbh, $diskgroup, $volume);
  if ($return_code == USM_FAIL)
  {
    # error message generated in asm_lib_asm_disable_volume()
    return USM_FAIL;
  }
 
  lib_asm_disconnect($dbh);

  return($return_code);
}
# end disable_vol

##########################
## internal helper functions
##########################

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

  lib_error_print(9134, "usage:");

  lib_error_print(9266, "The following environment variables must be set:");
  lib_error_print(9267, " _ORA_NAME            ora.<diskgroup>.<volume>.acfs");
  lib_error_print(9268, " _ORA_VOLUME_DEVICE   <ADVM_device_name>");
  lib_error_print(9269, " _ORA_MOUNTPOINT_PATH <mount_point_name>");

  if (defined($DEBUG))
  {
    # only used during development for testing
    lib_error_print(9270, "or");
    lib_error_print(9271, " start [-d device -g diskgroup -m mount_point]");
    lib_error_print(9272, " stop [-m mount_point]");
    lib_error_print(9273, " check [-m mount_point]");
    lib_error_print(9132, 
                    "        [-h]             - print help/usage information");
    lib_error_print(9131,
                    "        [-s]             - silent mode (error messages only)");
  }
  exit($exit);
}

main();

# vim:ts=4:expandtab
