#!/usr/local/bin/perl
# 
# $Header: has/utl/ochadctl.pl /st_has_12.2.0.1.0/2 2016/11/04 15:36:00 wmiyoshi Exp $
#
# ochadctl.pl
# 
# Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      ochadctl.pl - Utility script to control OCHAD
#
#    DESCRIPTION
#      <short description of component this file declares/defines>
#
#    NOTES
#      <other useful comments, qualifications, etc.>
#
#    MODIFIED   (MM/DD/YY)
#    wmiyoshi    11/03/16 - Backport wmiyoshi_bug-24584779 from main
#    wmiyoshi    11/01/16 - Increase JVM max heap size to 512MB
#    wmiyoshi    11/01/16 - Backport wmiyoshi_bug-24364355 from
#                           st_has_12.2.0.1.0
#    wmiyoshi    08/25/16 - Bug 24364355 - reduce ParallelGCThreads to 1
#    wmiyoshi    07/19/16 - Set JVM option ParallelGCThreads
#    wmiyoshi    05/19/16 - Bug 23255143 - pid matching issue in agent
#    wmiyoshi    04/29/16 - Bug 23094976 - remove pid checking (done in agent)
#    wmiyoshi    04/10/16 - Add cha-diag-msg.jar in classpath
#    wmiyoshi    08/14/15 - Creation
# 

use strict;
use warnings;
use English;   # Sets $OSNAME, $OS_ERROR, $PERL_VERSION
use Config;
use File::Spec::Functions;

# Perl status codes
use constant {
  SUCC_CODE => 0 ,
  FAIL_CODE => 1 ,
  ERROR_PID => -1 ,
  NULL_PID  => -2
};

# Agent script entry status codes (CLSAGFW_AE_*)
use constant {
  ENTRY_SUCCESS => 0 ,
  ENTRY_FAIL => 1
};

# Agent script check status codes (CLSAGFW_*)
use constant {
  STATE_ONLINE => 0 ,
  STATE_OFFLINE => 1 ,
  STATE_UNKNOWN => 3 ,
  STATE_FAILED => 5
};

# variables
my $oracle_home = $ENV{"ORACLE_HOME"};
my $jlib = catfile($oracle_home, "jlib");
my $hostname = $ENV{"HOSTSTR"};
my $pid_file = catfile($oracle_home, "cha", "init", $hostname . ".pid");
my $debug_out_set = 0;

# USR_ORA_ENV keys
my $isleep_interval_key = "ISLEEP_INTERVAL";
my $min_heap_size_key = "MIN_HEAP_SIZE";
my $min_perm_size_key = "MIN_PERM_SIZE";
my $max_heap_size_key = "MAX_HEAP_SIZE";
my $max_perm_size_key = "MAX_PERM_SIZE";
my $script_debug_key = "SCRIPT_DEBUG";
my $server_options_key = "SERVER_OPTIONS";
my $srvm_trace_level_key = "SRVM_TRACE_LEVEL";
my $parallel_gc_threads_key = "PARALLEL_GC_THREADS";

my %envhash;

sub set_debug_out()
{
  $debug_out_set = 1;
}

sub unset_debug_out()
{
  $debug_out_set = 0;
}

sub debug_out
#---------------------------------------------------------------------
# Function: Print debugging list if set_debug_out has been called
#
# Args    : Debugging list to print
#
# Returns : None
#---------------------------------------------------------------------
{
  if ($debug_out_set == 1)
  {
    print STDOUT "[ochadctl debug]";
    foreach (@_)
    {
      print STDOUT " ", $_;
    }
    print STDOUT "\n";
  }
}

sub dispatch_actions
#---------------------------------------------------------------------
# Function: Dispatch actions
#
# Args    : The action to be performed ("start", "clean")
#           Optional second argument (for "clean", time to wait after SIGQUIT)
#
# Returns : Agent status code (see oc4jctl_common package)
#---------------------------------------------------------------------
{
  # The first argument is the action to be performed
  my ($action, $timeout) = @_;
  my $rc;

  debug_out("... BEGIN_DEBUG [Action= $action] ...");

  if ($action eq "start")
  {
    $rc = ochad_start();
    debug_out("... START - Return code = $rc ...");
  }
  elsif ($action eq "clean")
  {
    $rc = ochad_clean($timeout);
    debug_out("... CLEAN - Return code = $rc ...");
  }
  else
  {
    $rc = ENTRY_FAIL;
    debug_out("... UNKNOWN ACTION IGNORED ...");
  }
  debug_out("... END_DEBUG [Action=$action] ...");

  return $rc;
}

sub read_pid_from_pid_file
#---------------------------------------------------------------------
# Function: Read the PID from the PID file
#
# Args    : None
#
# Returns : PID (if PID file is found)
#           NULL_PID (if PID file is not found)
#---------------------------------------------------------------------
{
  my $pid = NULL_PID;

  if (-e $pid_file && -r $pid_file)
  {
    open(PF, $pid_file);
    while(<PF>)
    {
      chomp;
      $pid = $_;
    }
    close(PF);
  }
  return $pid;
}

sub is_ochad_process
#---------------------------------------------------------------------
# Function: Test if the process is ochad
#
# Args    : PID
#
# Returns : 0 (if it's not ochad process)
#           1 (if it's ochad process)
#---------------------------------------------------------------------
{
  my ($pid) = @_;
  my $ochad_main_class = "CHADDriver";
  my $file = catfile('/proc', $pid, 'cmdline');

  open(my $fh, "<$file") or die("unable to open $file");

  while (<$fh>) {
    if ($_ =~ $ochad_main_class) {
      close($fh);
      return 1;
    }
  }

  close($fh);
  return 0;
}

sub send_signal
#---------------------------------------------------------------------
# Function: Send check, quit or kill signal
#
# Args    : Type of signal to send ("check", "quit" or "kill")
#           Process number
#
# Returns : SUCC_CODE (for "quit" and "kill", operation initiated;
#                      for "check", process still under control)
#           FAIL_CODE (operation not implemented)
#           ERROR_PID (process no longer under control)
#           NULL_PID  (process no longer exists)
#---------------------------------------------------------------------
{
  my $sig_rc = FAIL_CODE;
  my ($action, $pid) = @_;

  if ($action eq "check")
  {
    if (kill 0 => $pid)
    {
      $sig_rc = SUCC_CODE;
    }
    else
    {
      debug_out("... could not send signal 0 to process $pid. $! ...");
      $sig_rc = NULL_PID;
    }
  }
  elsif ($action eq "quit")
  {
    kill SIGQUIT => $pid;
    $sig_rc = SUCC_CODE;
  }
  elsif ($action eq "kill")
  {
    kill SIGKILL => $pid;
    $sig_rc = SUCC_CODE;
  }
  return $sig_rc;
}

sub spawn_process
#---------------------------------------------------------------------
# Function: Start command asynchronously in the background
#
# Args    : List with program name and its arguments
#
# Returns : CHILD PID (successful)
#           ERROR_PID (error condition)
#---------------------------------------------------------------------
{
  if ($OSNAME eq "MSWin32") {
    require Win32::Process;
    my $proc;

    if (Win32::Process::Create($proc, $_[0], "@_", 0, 0, ".")) {
      return $proc->GetProcessID();
    }
    else {
      print STDERR "Could not spawn child: $!";
      return ERROR_PID;
    }
  }
  else {
    my $pid = fork;
    if (!defined($pid)) {
      print STDERR "fork failed: $!\n";
      return ERROR_PID;
    }

    if ($pid == 0) { # child
      exec(@_) || die "exec failed $!";
    }

    return $pid;
  }
}

sub ochad_start
#---------------------------------------------------------------------
# Function: Perform start action
#
# Args    : None
#
# Returns : ENTRY_SUCCESS  (action succeeded)
#           ENTRY_FAIL     (action failed)
#---------------------------------------------------------------------
{
  print STDOUT "Start OCHAD\n";

  my $min_heap_size = $envhash{$min_heap_size_key};
  my $max_heap_size = $envhash{$max_heap_size_key};
  my $server_options = $envhash{$server_options_key};

  # Collect heap options

  my @jre_heap_options = ("-Xms$min_heap_size", "-Xmx$max_heap_size");
  if (defined($ENV{"JRE_HEAP_OPTIONS"}))
  {
    @jre_heap_options = (split(/\s+/, $ENV{"JRE_HEAP_OPTIONS"}),
                         @jre_heap_options);
  }
  if (defined($envhash{$min_perm_size_key}))
  {
    @jre_heap_options = (@jre_heap_options,
                         "-XX:PermSize=" . $envhash{$min_perm_size_key});
  }
  if (defined($envhash{$max_perm_size_key}))
  {
    @jre_heap_options = (@jre_heap_options,
                         "-XX:MaxPermSize=" . $envhash{$max_perm_size_key});
  }

  # Collect GC options
  my @jre_gc_options
      = ("-XX:ParallelGCThreads=" . $envhash{$parallel_gc_threads_key});

  # Collect properties

  my @java_property_defs_base
      = ("-Djava.awt.headless=true", "-Ddisable.checkForUpdate=true");

  my @srvm_tracing; # (SRVM_TRACE_LEVEL=1-11, 1= finest, 11=info)
  if (defined($ENV{"SRVM_TRACE_LEVEL"}))
  {
    @srvm_tracing = ("-DTRACING.ENABLED=true",
                     "-DTRACING.LEVEL=". $ENV{"SRVM_TRACE_LEVEL"});
  }
  elsif (defined($envhash{$srvm_trace_level_key}))
  {
    @srvm_tracing = ("-DTRACING.ENABLED=true",
                     "-DTRACING.LEVEL=". $envhash{$srvm_trace_level_key});
  }
  else
  {
    @srvm_tracing = ("-DTRACING.ENABLED=false");
  }

  my @ochad_tracing;
  if (defined($ENV{"OCHAD_LOGGING_LEVEL"}))
  {
    @ochad_tracing
      = ("-Doracle.cha.logging.level=" . $ENV{"OCHAD_LOGGING_LEVEL"});
  }

  my @java_property_defs = (@java_property_defs_base,
                            @srvm_tracing,
                            @ochad_tracing);

  my $sleep_interval = $envhash{$isleep_interval_key};
  my $start_deployment_interval = 15;

  my $classpath =         catfile($jlib, "cha.jar") .
      $Config{path_sep} . catfile($jlib, "chaconfig.jar") .
      $Config{path_sep} . catfile($jlib, "cha-diag-msg.jar") .
      $Config{path_sep} . catfile($jlib, "clsce.jar") .
      $Config{path_sep} . catfile($jlib, "srvm.jar") .
      $Config{path_sep} . catfile($jlib, "srvmhas.jar") .
      $Config{path_sep} . catfile($jlib, "srvmasm.jar") .
      $Config{path_sep} . catfile($jlib, "netcfg.jar") .
      $Config{path_sep} . catfile($oracle_home, "jdbc", "lib", "ojdbc8.jar") .
      $Config{path_sep} . catfile($oracle_home, "ucp", "lib", "ucp.jar") .
      $Config{path_sep} . catfile($jlib, "fte.jar") .
      $Config{path_sep} . catfile($jlib, "jai_core.jar") .
      $Config{path_sep} . catfile($jlib, "mlibwrapper_jai.jar") .
      $Config{path_sep} . catfile($jlib, "vecmath.jar") .
      $Config{path_sep} . catfile($jlib, "jai_codec.jar") .
      $Config{path_sep} . catfile($jlib, "jh.jar");

  my $cha_main_class = "oracle.cha.server.CHADDriver";

  my @command_parms =  (split(/\s+/, $ENV{"JRE_OPTIONS"}),
                        split(/\s+/, $ENV{"JRE_OPTIONS_SERVER"}),
                        split(/\s+/, $server_options),
                        @jre_heap_options, @java_property_defs,
                        @jre_gc_options,
                        "-cp", $classpath, $cha_main_class);

  my $jre = catfile($ENV{"JAVA_HOME"}, "bin", "java");
  if ($OSNAME eq "MSWin32") {
    $jre .= ".exe";
  }

  my $pid = spawn_process($jre, @command_parms);

  debug_out("... OCHAD started (PID = $pid) ...");

  return ENTRY_SUCCESS;
}

sub ochad_clean
#---------------------------------------------------------------------
# Function: Perform OCHAD clean action
#
# Args    : Number of seconds to wait after issuing SIGQUIT
#
# Returns : ENTRY_SUCCESS  (action succeeded)
#
# Notes   : The clean is implemented in this script as a workaround for
#           bug 23317399 - agent pid matching not working. This function
#           and related code should be cleaned up when this bug is fixed.
#---------------------------------------------------------------------
{
  print STDOUT "Clean OCHAD\n";

  my ($sigquit_sleep_interval) = @_;
  my $iter_cnt = 0;
  my $sleep_interval = $envhash{$isleep_interval_key};

  my $pid = read_pid_from_pid_file();
  if ($pid == NULL_PID)
  {
    debug_out("... could not find file $pid_file ... ");
    return ENTRY_SUCCESS;
  }

  if (send_signal("check", $pid) != SUCC_CODE) {
    debug_out("... could not find OCHAD process pid=$pid ...");
    return ENTRY_SUCCESS;
  }

  if (!is_ochad_process($pid)) {
    debug_out("... not an OCHAD process pid=$pid ...");
    return ENTRY_SUCCESS;
  }

  # Send SIGQUIT (to get a dump)
  if (send_signal("quit", $pid) != SUCC_CODE)
  {
    debug_out("... could not send SIGQUIT (pid=$pid) ...");
  }

  # Sleep on request after SIGQUIT or jstack command
  if ($sigquit_sleep_interval >= 0)
  {
    sleep($sigquit_sleep_interval);
  }

  # Send SIGKILL
  if (send_signal("kill", $pid) != SUCC_CODE) {
    debug_out("... could not send SIGQUIT (pid=$pid) ...");
    return ENTRY_FAIL;
  }

  debug_out("... Cleaned OCHAD (pid=$pid) ...");

  return ENTRY_SUCCESS;
}

sub init_globals
#---------------------------------------------------------------------
# Function: Initialize globals
#
# Args    : None
#
# Returns : SUCC_CODE (operation succeeded)
#           FAIL_CODE (operation failed)
#---------------------------------------------------------------------
{
  print STDOUT localtime() . "\n";

  if (defined($ENV{$script_debug_key}))
  {
    set_debug_out();
  }
  else
  {
    unset_debug_out();
  }

  # Initialize environment hash
  %envhash = ($isleep_interval_key => 1 ,
              $min_heap_size_key => "30M" ,
              $min_perm_size_key => undef ,
              $max_heap_size_key => "512M" ,
              $max_perm_size_key => undef ,
              $script_debug_key => undef ,
              $server_options_key => "" ,
              $srvm_trace_level_key => undef ,
              $parallel_gc_threads_key => "1" );

  # Force STDOUT and STDERR to be flushed for each I/O operation
  select(STDERR);
  $| = 1;
  select(STDOUT);
  $| = 1;

  # Read_environment variables into envhash
  foreach my $envkey (keys(%envhash))
  {
    if (defined($ENV{$envkey}))
    {
      debug_out("... env $envkey=$ENV{$envkey} ...");
      $envhash{$envkey} = $ENV{$envkey};
    }
  }

  return SUCC_CODE;
}

#---------------------------------------------------------------------
# Function: Main program
#
# Args    : The action to be performed ("start", "stop", "check", "clean")
#           Optional second argument (for "clean", time to wait after SIGQUIT)
#
# Returns : Agent status code (see oc4jctl_common package)
#---------------------------------------------------------------------

# Initialize globals

my $rc = init_globals();

if ($rc == FAIL_CODE)
{
  # If initialization fails, exit with STATE_UNKNOWN
  $rc = STATE_UNKNOWN;
}
else
{
  if (@ARGV < 2)
  {
    $rc = dispatch_actions($ARGV[0], "null");
  }
  else
  {
    $rc = dispatch_actions($ARGV[0], $ARGV[1]);
  }
}

exit $rc;
