#
#
# acfsreplcrs.pl
#
# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      acfsreplcrs.pl
#
#    DESCRIPTION
#
#    NOTES
#
#
use strict;
use acfslib;
use File::Spec::Functions;
use File::Basename;
use Time::Local;
BEGIN{ eval "use Win32::OLE('in')" };

shift; # Remove the command name as the first argument.
my $op    = shift;
my $proc  = shift;
my $site  = shift;
my $mntpt = shift;
my $primdev;
my $dirname;

my $os = $^O;

if ($os !~ /mswin.*/i )
{
  $primdev = shift;
 
  # If primdev is a symlink, get real path.  Get canonical path either way.

  if (-l $primdev)
  {
    $dirname = dirname($primdev);
    $primdev = File::Spec->rel2abs(readlink($primdev),$dirname);
  }
  else
  {
    $primdev = canonpath($primdev);
  }
  logx("op=$op, proc=$proc, site=$site, mnt=$mntpt pdev=$primdev\n");
}
else
{
  $primdev = "unsupported";
  logx("op=$op, proc=$proc, site=$site, mnt=$mntpt\n");
}

my $debug  = 0;
my $debuglog = catfile($ENV{T_WORK}, "crslog.lst");
my $exesuffix = "";  # Set in parse_args() if needed




# Check the arguments
parse_args();

my $bindir = catfile("$ENV{'ORA_CRS_HOME'}", "bin");

my $transportname = "acfsrepl_transport". "$exesuffix";
my $transportpath = catfile($bindir, $transportname);

my $monitorname   = "acfsrepl_monitor". "$exesuffix";
my $monitorpath   = catfile($bindir, $monitorname);

my $preapplyname  = "acfsrepl_preapply". "$exesuffix";
my $preapplypath  = catfile($bindir, $preapplyname);

my $applyname     = "acfsrepl_apply". "$exesuffix";
my $applypath     = catfile($bindir, $applyname);

my $initname      = "acfsrepl_initializer". "$exesuffix";
my $initpath      = catfile($bindir, $initname);

my $clonename     = "acfsrepl_dupd". "$exesuffix";
my $clonepath     = catfile($bindir, $clonename);

# Corresponds to USC_READYDIR
my $readyDir      = catfile($mntpt, ".ACFS", "repl", "ready" );
# Corresponds to USC_TERMFILE
my $termFile      = catfile($readyDir, "ReplicationTerminating" );

my %procpathl = (
          "transport" => $transportpath,
          "preapply"  => $preapplypath,
          "monitor"   => $monitorpath,
          "apply"     => $applypath,
          "init"      => $initpath,
          "dupd"      => $clonepath,
          "main"      => "PLACEHOLDER"
);

my @mainprocs = ("monitor" , "preapply");


SWITCH:
{
  if($op eq 'start') { start();  last SWITCH; }
  if($op eq 'stop' ) { stop();   last SWITCH; }
  if($op eq 'check') { check();  last SWITCH; }
  if($op eq 'clean') { clean();  last SWITCH; }
  else { print "Unknown option $op\n"; }
}



sub start
{
  my $startpath;

  # Check to see if the filesystem is mounted.
  if(is_mount_available($mntpt) == 0)
  {
    logx("Start: No filesystem mounted on $mntpt\n");
    exit(CLSAGFW_AE_FAIL);
  }

  if ($os !~ /mswin.*/i )
  {
    if(check_device_name() == 0)
    {
      logx("Start: Mismatched volume mounted on $mntpt\n");
      exit(CLSAGFW_AE_FAIL);
    }
  }

  if($proc eq "main")
  {
    my $bgproc;

    # Start the set of background daemon processes
    foreach $bgproc (@mainprocs)
    {
      $startpath = $procpathl{$bgproc};

      if(!defined($startpath))
      {
        logx("Unknown process to start - $bgproc\n");
        exit(CLSAGFW_AE_FAIL);
      }

      if($bgproc eq "preapply" && $site eq "primary")
      {
         logx("Skipping starting the preapply on the primary");
      }
      else
      {
        # Start the daemons only if they are not already running.
        if(!defined( get_daemon_pid($procpathl{$bgproc}, $mntpt)))
        {
          system("$procpathl{$bgproc} $mntpt");
          logx("Start (MAIN):  $procpathl{$bgproc} $mntpt\n");
        }
        else
        {
         logx("Process - $bgproc already running\n"); 
        }
      }
    }
  }
  else
  { 
    # Transport/Apply/Init
    $startpath = $procpathl{$proc};

    if(!defined($startpath))
    {
      logx("Unknown process to start - $proc\n");
      exit(CLSAGFW_AE_FAIL);
    }

    # Start the daemon only if it is not already running.
    if(!defined( get_daemon_pid($startpath, $mntpt)))
    {
      logx("Start ($proc, $mntpt, $startpath)\n");
      system("$startpath $mntpt");  
    }
    else
    {
      logx("Process - $proc already running\n"); 
    }
  }

  exit(CLSAGFW_AE_SUCCESS);
}



sub stop
{
  # Send the signal to the transport process. Wait for the existence 
  # of a 'stop' file in the replication 'ready' directory.
  my $procpath;
  my $pid;
  my $bgproc;
  my ($force) = @_;

  logx("Stopping replication process ($proc, $mntpt)\n");


  if($proc eq "main")
  {
    # Stop the set of background daemon processes
    $bgproc = "monitor";
    logx("attempting to stop $bgproc\n");
    $procpath = $procpathl{$bgproc};
    $pid = get_daemon_pid($procpath, $mntpt);

    if(defined($pid))
    {
      kill_pid($pid, $force);
      logx("finished killing proc $bgproc\n");
    }

    $bgproc = "preapply";
    logx("attempting to stop $bgproc\n");
    $procpath = $procpathl{$bgproc};
    $pid = get_daemon_pid($procpath, $mntpt);

    if(defined($pid))
    {
      kill_pid($pid, $force);
      logx("finished killing proc $bgproc\n");
    }

  }
  else
  {
    logx("Attempting to stop $proc\n");  
    $procpath = $procpathl{$proc};
    $pid = get_daemon_pid($procpath, $mntpt);

    if(defined($pid))
    {
      kill_pid($pid, $force);
    }
    else
    {
      logx("Process on mountpoint - $mntpt not running\n");
    }

  }

  exit(CLSAGFW_AE_SUCCESS);
}

sub kill_pid
{
  my ($dpid, $force) = @_;
  my  $waits    = 0;
  my  $waittime = 30; # Wait 30 seconds for the proc to die
  my  @pidsl;

  # Wait here until pid is gone

  if($os =~ /mswin.*/i )
  {
    undef $force;
  }

  do
  {
    undef @pidsl;
    # Get a pids list of daemon and its children
    @pidsl = get_daemon_children_pids($dpid);

    if(@pidsl)
    {
	  if(defined($force))
	  {
        logx("Sending kill -9 to pid $dpid\n");
	    kill 9, @pidsl;
	  }
	  else
	  {
        if($os =~ /mswin.*/i )
        {
	      logx("Sending kill ABRT to pids @pidsl\n");
          kill ABRT => @pidsl;
        }
        else
        {
          logx("Sending kill USR1 to pid $dpid\n");
          kill USR1 => $dpid;
          foreach my $pid (@pidsl)
          {
            if ($pid != $dpid)
            {
              logx("Sending kill TERM to pid $pid\n");
              kill TERM => $pid  if ($pid != $dpid);
            }
          }
        }
	  }
    }

    @pidsl = get_daemon_children_pids($dpid);
    if(@pidsl)
    {
	  logx("Sleeping for pid = $dpid ($waits)\n");
	  sleep(1);
	  $waits++;
    }
    else
    {
      logx("PID is DEAD\n");
	  $waits = $waittime; # Break out of the loop
    }
  } while($waits < $waittime);

  undef @pidsl;
  @pidsl = get_daemon_children_pids($dpid);
  if(@pidsl)
  {
    logx("Unable to kill pids \n @pidsl. \nForcing process kill. \n");
    if($os !~ /mswin.*/i )
    {
	  kill 9, @pidsl;
    }
  }
}

sub check
{
  my $pid;

  logx("Check: Proc=$proc Mntpt=$mntpt");

  # The transport process is a local resource. During init we start it up 
  # on all nodes and only those nodes that have the FS mounted are kept 
  # alive.  We detect this here, and return PLANNED_OFFLINE so that CRS 
  # will not try to restart or monitor the process on this node.
  if((is_mount_available($mntpt) == 0) &&
     ($proc eq "transport"))
  {
    logx("Check: Planned offline $mntpt\n");
    exit(CLSAGFW_PLANNED_OFFLINE);
  }

  if($proc eq "main")
  {
    my $count = 0;
    my $bgproc;
    my $proccount = @mainprocs;

    # Adjust expected process counts for various situations
    if($site eq "primary")
    {
      # preapply doesn't run on the primary
      $proccount--;
    }
    elsif( $site eq "standby" && -f $termFile )
    {
      # preapply may or may not be running. Either way is OK.  If it is
      # running, it's running for the exit ...
      $proccount--;
    }

    # Check the set of non-root daemon processes.
    foreach $bgproc (@mainprocs)
    {
      if($bgproc eq "preapply" && $site eq "primary")
      {
        # Skip checking for the preapply on the primary
        next;
      }

      if(defined( get_daemon_pid($procpathl{$bgproc}, $mntpt)))
      {
        $count++;
        logx("\t $bgproc daemon is running\n");
      }
      else
      {
        logx("\t $bgproc daemon is not running\n");
      }
    }

    # Return status based on the number of running processes
    if($count == 0)        {
      logx("\t MAIN is returning UNPLANNED OFFLINE\n");
      exit(CLSAGFW_UNPLANNED_OFFLINE);
    }
    if($count < $proccount)
    { 
      logx("\t MAIN is returning FAILED\n");
      exit(CLSAGFW_FAILED);
    }
    exit(CLSAGFW_ONLINE);
  }
  elsif ($proc eq "clone" or $proc eq "dupd" ) 
  {
    my $procpath = $procpathl{$proc};
    $pid = get_daemon_pid($procpath, $mntpt);

    if(defined($pid))
    {
      logx("Found $proc daemon PID: $pid\n");
      exit(CLSAGFW_ONLINE);
    }
    else
    {
      if ( -f $termFile )
      {
        # It's expected that dupd isn't running because we're in the
        # process of terminating.
        logx("$proc daemon not running. Termination in progress. (OK)\n");
        exit(CLSAGFW_PLANNED_OFFLINE);
      }
      else
      {
        logx("$proc daemon not running.\n");
        exit(CLSAGFW_UNPLANNED_OFFLINE);
      }
    }
  }
  else
  {
    my $procpath = $procpathl{$proc};
    $pid = get_daemon_pid($procpath, $mntpt);

    if(defined($pid))
    {
      logx("Found $proc daemon PID: $pid\n");
      exit(CLSAGFW_ONLINE);
    }
    else
    {
      logx("Daemon process ($proc) not running\n");
      exit(CLSAGFW_UNPLANNED_OFFLINE);
    }
  }
}

sub clean
{
  stop(1); #Force
  exit(CLSAGFW_AE_SUCCESS);
}


# Return the process ID of the daemon process, if any, undef otherwise.
sub get_daemon_pid
{
  my ($procpath, $mntpt) = @_;
  my  $pid;

  if($os =~ /mswin.*/i)
  {
     use constant wbemFlagReturnImmediately => 0x10;
     use constant wbemFlagForwardOnly => 0x20;

     my $command;
     my $computer = "localhost";
     my $objWMIService = Win32::OLE->GetObject("winmgmts:\\\\$computer\\root\\CIMV2") or die "WMI connection failed.\n";
     my $colItems = $objWMIService->ExecQuery("SELECT * FROM Win32_Process", "WQL", wbemFlagReturnImmediately | wbemFlagForwardOnly);

     # Convert the path into a string that we can pattern match with
     my $metapath = quotemeta($procpath);
     my $metamnt  = quotemeta($mntpt);
     
     foreach my $objItem (in $colItems)
     {
       $command = $objItem->{CommandLine};
       if($command =~ /$metapath / && ($command =~ /$metamnt / || $command =~ /$metamnt$/))
       {
         return  $objItem->{ProcessId};
       }
     }

     # Process not found
     return undef;
  }
  elsif($os =~ /solaris/i)
  {
     my $cmdname = basename($procpath);
     my @pids = split(' ', `ps -e | grep acfsrepl | awk '{ print \$1 }'`);
     foreach $pid (@pids)
     {
        my $command = `pargs -l $pid 2>/dev/null`;
        if($command =~ /($cmdname)/ && $command =~ /\s($mntpt)(\s|$)/)
        {
           return $pid;
        }
     }
     return undef;
  }
  elsif($os =~ /aix/i)
  {
     $pid = `ps axeww | grep \"$procpath\" | grep -w \"$mntpt\" | grep -v grep | head -1 | awk '{ print \$1}'`;
     if($pid)
     {
        return $pid
     }
     else
     {
        return undef;
     }
  }
  else
  {
     $pid = `ps -ef | grep \"$procpath\" | grep -w \"$mntpt\" | grep -v grep | head -1 | awk '{ print \$2}'`;

     #logx("\tReturning pid: $pid  ($procpath)");
     if($pid){ return $pid }
     else{ return undef; }
  }
}

# Return the process ID of the daemon process, if any, undef otherwise.
sub get_daemon_children_pids
{
  my ($dpid) = @_;
  my  @pidsl;
  my @pidsl1;

  logx("Get daemon pid $dpid and its children\n");

  if($os =~ /mswin.*/i)
  {
    use constant wbemFlagReturnImmediately => 0x10;
    use constant wbemFlagForwardOnly => 0x20;

    my $computer = "localhost";
    my $objWMIService = Win32::OLE->GetObject("winmgmts:\\\\$computer\\root\\CIMV2") or die "WMI connection failed.\n";
    my $colItems = $objWMIService->ExecQuery("SELECT * FROM Win32_Process", "WQL", wbemFlagReturnImmediately | wbemFlagForwardOnly);

    foreach my $objItem (in $colItems)
    {
      if($objItem->{ProcessId} == $dpid || $objItem->{ParentProcessId} == $dpid)
      {
        push (@pidsl, $objItem->{ProcessId});
      }
    }
  }
  else
  {
    @pidsl = `ps -ef | awk '{ print \$2 \" \" \$3}' | grep -w \"$dpid\" | grep -v grep | awk '{ print \$1}'`;
  }

  if(@pidsl)
  {  
    logx("Return a List of pids \n @pidsl\n");
  }
  else
  {
    logx("List is empty. return undef\n");
  }   
  return @pidsl;
}

# Debug print
sub logx
{
  my $arg = shift;

  # TODO: Verify that a print statement goes to the CRS log on Windows.
  # Then remove the Windows conditional
  if ($os =~ /mswin.*/i )
  {
    open  LOG, ">> C:\\log.txt" or die "Can't open log.txt: $!";
    print LOG "$arg\n";
    close LOG;
    print "$arg\n";
  }
  else
  {
     # This will go to the CRS log
     print "REPL: $arg";
  }
}

sub check_device_name
{
  my $ret         = -1;
  my $devfound;

  if ($os =~ /mswin.*/i )
  {
    $devfound = `acfsutil.exe info fs /o primaryvolume $mntpt`;
    $primdev =~ s/^\\\\\.\\+//; # Remove Windows prefix
  }
  else
  {
    $devfound = `acfsutil info fs -o primaryvolume $mntpt`;
    $devfound =~ s/\s*$//; # Remove trailing newline/whitespace
 
    # If devfound is a symlink, get real path.  Get canonical path either way.

    if (-l $devfound)
    {
      $dirname = dirname($devfound);
      $devfound = File::Spec->rel2abs(readlink($devfound),$dirname);
    }
    else
    {
      $devfound = canonpath($devfound);
    }
  }

  $devfound =~ s/^\s+|\s+$//;    # Remove surrounding whitespace
  $primdev =~  s/^\s+|\s+$//;    # Remove surrounding whitespace
  
  if($devfound eq $primdev)
  {
     $ret = 1;
  }
  else
  {
     $ret = 0;
  }

  if($ret == -1)
  {
    print "Cannot find primary device\n";
    $ret = 1; # Default to SUCCESS; since the FS is mounted, it is likely
              # mounted in the correct place.
  }

  return $ret;
}

sub is_mount_available
{
  my $cmd;
  my $out;

  if ($os =~ /mswin.*/i )
  {
    $cmd = "acfsutil.exe info fs /o isavailable $mntpt";
  }
  else
  {
    $cmd = "acfsutil info fs -o isavailable $mntpt";
  }

  $out = `$cmd`;

  if($out =~ s/^1$//g)
  {
    return 1;
  }
  return 0;
}

# Verify arguments and put them in proper format
sub parse_args
{
  if ($os =~ /mswin.*/i )
  {
    $exesuffix = ".exe";
    return;
  }
  else
  {
    # Remove the last '/' of the mountpoint if one exists
    if($mntpt =~ m/\/$/)
    {
      chop($mntpt);
    }
  }
}

