# -*- cperl -*-

# This is a library file used by the Perl version of mysql-test-run,
# and is part of the translation of the Bourne shell script with the
# same name.

use Carp qw(cluck);
use strict;

use POSIX ":sys_wait_h";

sub mtr_run ($$$$$$);
sub mtr_spawn ($$$$$$);
sub mtr_stop_mysqld_servers ($$);
sub mtr_kill_leftovers ();

# static in C
sub spawn_impl ($$$$$$$);

##############################################################################
#
#  Execute an external command
#
##############################################################################

# This function try to mimic the C version used in "netware/mysql_test_run.c"
# FIXME learn it to handle append mode as well, a "new" flag or a "append"

sub mtr_run ($$$$$$) {
  my $path=       shift;
  my $arg_list_t= shift;
  my $input=      shift;
  my $output=     shift;
  my $error=      shift;
  my $pid_file=   shift;

  return spawn_impl($path,$arg_list_t,1,$input,$output,$error,$pid_file);
}

sub mtr_spawn ($$$$$$) {
  my $path=       shift;
  my $arg_list_t= shift;
  my $input=      shift;
  my $output=     shift;
  my $error=      shift;
  my $pid_file=   shift;

  return spawn_impl($path,$arg_list_t,0,$input,$output,$error,$pid_file);
}


##############################################################################
#
#  If $join is set, we return the error code, else we return the PID
#
##############################################################################

sub spawn_impl ($$$$$$$) {
  my $path=       shift;
  my $arg_list_t= shift;
  my $join=       shift;
  my $input=      shift;
  my $output=     shift;
  my $error=      shift;
  my $pid_file=   shift;                 # FIXME

  # FIXME really needing a PATH???
  # $ENV{'PATH'}= "/bin:/usr/bin:/usr/local/bin:/usr/bsd:/usr/X11R6/bin:/usr/openwin/bin:/usr/bin/X11:$ENV{'PATH'}";

  $ENV{'TZ'}=             "GMT-3";         # for UNIX_TIMESTAMP tests to work
  $ENV{'LC_COLLATE'}=     "C";
  $ENV{'MYSQL_TEST_DIR'}= $::glob_mysql_test_dir;
  $ENV{'MASTER_MYPORT'}=  $::opt_master_myport;
  $ENV{'SLAVE_MYPORT'}=   $::opt_slave_myport;
# $ENV{'MYSQL_TCP_PORT'}= '@MYSQL_TCP_PORT@'; # FIXME
  $ENV{'MYSQL_TCP_PORT'}= 3306;
  $ENV{'MASTER_MYSOCK'}=  $::master->[0]->{'path_mysock'};

  if ( $::opt_script_debug )
  {
    print STDERR "\n";
    print STDERR "#### ", "-" x 78, "\n";
    print STDERR "#### ", "STDIN  $input\n" if $input;
    print STDERR "#### ", "STDOUT $output\n" if $output;
    print STDERR "#### ", "STDERR $error\n" if $error;
    if ( $join )
    {
      print STDERR "#### ", "run";
    }
    else
    {
      print STDERR "#### ", "spawn";
    }
    print STDERR "$path ", join(" ",@$arg_list_t), "\n";
    print STDERR "#### ", "-" x 78, "\n";
  }

  my $pid= fork();

  if ( $pid )
  {
    # Parent, i.e. the main script
    if ( $join )
    {
      # We run a command and wait for the result
      # FIXME this need to be improved
      waitpid($pid,0);
      my $exit_value=  $? >> 8;
      my $signal_num=  $? & 127;
      my $dumped_core= $? & 128;
      if ( $signal_num )
      {
        mtr_error("spawn got signal $signal_num");
      }
      if ( $dumped_core )
      {
        mtr_error("spawn dumped core");
      }
      return $exit_value;
    }
    else
    {
      # We spawned a process we don't wait for
      return $pid;
    }
  }
  else
  {
    # Child, redirect output and exec
    # FIXME I tried POSIX::setsid() here to detach and, I hoped,
    # avoid zombies. But everything went wild, somehow the parent
    # became a deamon as well, and was hard to kill ;-)
    # Need to catch SIGCHLD and do waitpid or something instead......

    $SIG{INT}= 'DEFAULT';         # Parent do some stuff, we don't

    if ( $output )
    {
      if ( ! open(STDOUT,">",$output) )
      {
        mtr_error("can't redirect STDOUT to \"$output\": $!");
      }
    }
    if ( $error )
    {
      if ( $output eq $error )
      {
        if ( ! open(STDERR,">&STDOUT") )
        {
          mtr_error("can't dup STDOUT: $!");
        }
      }
      else
      {
        if ( ! open(STDERR,">",$error) )
        {
          mtr_error("can't redirect STDERR to \"$output\": $!");
        }
      }
    }
    if ( $input )
    {
      if ( ! open(STDIN,"<",$input) )
      {
        mtr_error("can't redirect STDIN to \"$input\": $!");
      }
    }
    exec($path,@$arg_list_t);
  }
}

##############################################################################
#
#  Kill processes left from previous runs
#
##############################################################################

sub mtr_kill_leftovers () {

  # First, kill all masters and slaves that would conflict with
  # this run. Make sure to remove the PID file, if any.

  my @args;

  for ( my $idx; $idx < 2; $idx++ )
  {
    push(@args,{
                pid      => 0,          # We don't know the PID
                pidfile  => $::master->[$idx]->{'path_mypid'},
                sockfile => $::master->[$idx]->{'path_mysock'},
                port     => $::master->[$idx]->{'path_myport'},
               });
  }

  for ( my $idx; $idx < 3; $idx++ )
  {
    push(@args,{
                pid       => 0,         # We don't know the PID
                pidfile   => $::slave->[$idx]->{'path_mypid'},
                sockfile  => $::slave->[$idx]->{'path_mysock'},
                port      => $::slave->[$idx]->{'path_myport'},
               });
  }

  mtr_stop_mysqld_servers(\@args, 1);

  # We scan the "var/run/" directory for other process id's to kill
  my $rundir= "$::glob_mysql_test_dir/var/run"; # FIXME $path_run_dir or something

  if ( -d $rundir )
  {
    opendir(RUNDIR, $rundir)
      or mtr_error("can't open directory \"$rundir\": $!");

    my @pids;

    while ( my $elem= readdir(RUNDIR) )
    {
      my $pidfile= "$rundir/$elem";

      if ( -f $pidfile )
      {
        my $pid= mtr_get_pid_from_file($pidfile);
        if ( ! unlink($pidfile) )
        {
          mtr_error("can't remove $pidfile");
        }
        push(@pids, $pid);
      }
    }
    closedir(RUNDIR);

    start_reap_all();

    if ( $::glob_cygwin_perl )
    {
      # We have no (easy) way of knowing the Cygwin controlling
      # process, in the PID file we only have the Windows process id.
      system("kill -f " . join(" ",@pids)); # Hope for the best....
    }
    else
    {
      my $retries= 10;                    # 10 seconds
      do
      {
        kill(9, @pids);
      } while ( $retries-- and  kill(0, @pids) );

      if ( kill(0, @pids) )
      {
        mtr_error("can't kill processes " . join(" ", @pids));
      }
    }

    stop_reap_all();
  }
}

##############################################################################
#
#  Shut down mysqld servers
#
##############################################################################

# To speed things we kill servers in parallel.
# The argument is a list of 'pidfiles' and 'socketfiles'.
# We use the pidfiles and socketfiles to try to terminate the servers.
# This is not perfect, there could still be other server processes
# left.

# Force flag is to be set only for killing mysqld servers this script
# didn't create in this run, i.e. initial cleanup before we start working.
# If force flag is set, we try to kill all with mysqladmin, and
# give up if we have no PIDs.

# FIXME On some operating systems, $srv->{'pid'} and $srv->{'pidfile'}
#       will not be the same PID. We need to try to kill both I think.

sub mtr_stop_mysqld_servers ($$) {
  my $spec=  shift;
  my $force= shift;

  # ----------------------------------------------------------------------
  # If the process was not started from this file, we got no PID,
  # we try to find it in the PID file.
  # ----------------------------------------------------------------------

  my $any_pid= 0;                     # If we have any PIDs

  foreach my $srv ( @$spec )
  {
    if ( ! $srv->{'pid'} and -f $srv->{'pidfile'} )
    {
      $srv->{'pid'}= mtr_get_pid_from_file($srv->{'pidfile'});
    }
    if ( $srv->{'pid'} )
    {
      $any_pid= 1;
    }
  }

  # If the processes where started from this script, and we know
  # no PIDs, then we don't have to do anything.

  if ( ! $any_pid and ! $force )
  {
    # cluck "This is how we got here!";
    return;
  }

  # ----------------------------------------------------------------------
  # First try nice normal shutdown using 'mysqladmin'
  # ----------------------------------------------------------------------

  start_reap_all();                   # Don't require waitpid() of children

  foreach my $srv ( @$spec )
  {
    if ( -e $srv->{'sockfile'} or $srv->{'port'} )
    {
      # FIXME wrong log.....
      # FIXME, stderr.....
      # Shutdown time must be high as slave may be in reconnect
      my $args;

      mtr_init_args(\$args);

      mtr_add_arg($args, "--no-defaults");
      mtr_add_arg($args, "-uroot");
      if ( -e $srv->{'sockfile'} )
      {
        mtr_add_arg($args, "--socket=%s", $srv->{'sockfile'});
      }
      if ( $srv->{'port'} )
      {
        mtr_add_arg($args, "--port=%s", $srv->{'port'});
      }
      mtr_add_arg($args, "--connect_timeout=5");
      mtr_add_arg($args, "--shutdown_timeout=70");
      mtr_add_arg($args, "shutdown");
      # We don't wait for termination of mysqladmin
      mtr_spawn($::exe_mysqladmin, $args,
                "", $::path_manager_log, $::path_manager_log, "");
    }
  }

  # Wait for them all to remove their pid and socket file

 PIDSOCKFILEREMOVED:
  for (my $loop= $::opt_sleep_time_for_delete; $loop; $loop--)
  {
    my $pidsockfiles_left= 0;
    foreach my $srv ( @$spec )
    {
      if ( -e $srv->{'sockfile'} or -f $srv->{'pidfile'} )
      {
        $pidsockfiles_left++;          # Could be that pidfile is left
      }
    }
    if ( ! $pidsockfiles_left )
    {
      last PIDSOCKFILEREMOVED;
    }
    mtr_debug("Sleep for 1 second waiting for pid and socket file removal");
    sleep(1);                          # One second
  }

  # ----------------------------------------------------------------------
  # If no known PIDs, we have nothing more to try
  # ----------------------------------------------------------------------

  if ( ! $any_pid )
  {
    stop_reap_all();
    return;
  }

  # ----------------------------------------------------------------------
  # We may have killed all that left a socket, but we are not sure we got
  # them all killed. If we suspect it lives, try nice kill with SIG_TERM.
  # Note that for true Win32 processes, kill(0,$pid) will not return 1.
  # ----------------------------------------------------------------------

 SIGNAL:
  foreach my $sig (15,9)
  {
    my $process_left= 0;
    foreach my $srv ( @$spec )
    {
      if ( $srv->{'pid'} and
           ( -f $srv->{'pidfile'} or kill(0,$srv->{'pid'}) ) )
      {
        $process_left++;
        mtr_warning("process $srv->{'pid'} not cooperating, " .
                    "will send signal $sig to process");
        kill($sig,$srv->{'pid'});       # SIG_TERM
      }
      if ( ! $process_left )
      {
        last SIGNAL;
      }
    }
    mtr_debug("Sleep for 5 seconds waiting for processes to die");
    sleep(5);                           # We wait longer than usual
  }

  # ----------------------------------------------------------------------
  # Now, we check if all we can find using kill(0,$pid) are dead,
  # and just assume the rest are. We cleanup socket and PID files.
  # ----------------------------------------------------------------------

  {
    my $errors= 0;
    foreach my $srv ( @$spec )
    {
      if ( $srv->{'pid'} )
      {
        if ( kill(0,$srv->{'pid'}) )
        {
          # FIXME In Cygwin there seem to be some fast reuse
          # of PIDs, so dying may not be the right thing to do.
          $errors++;
          mtr_warning("can't kill process $srv->{'pid'}");
        }
        else
        {
          # We managed to kill it at last
          # FIXME In Cygwin, we will get here even if the process lives.

          # Not needed as we know the process is dead, but to be safe
          # we unlink and check success in two steps. We first unlink
          # without checking the error code, and then check if the
          # file still exists.

          foreach my $file ($srv->{'pidfile'}, $srv->{'sockfile'})
          {
            unlink($file);
            if ( -e $file )
            {
              $errors++;
              mtr_warning("couldn't delete $file");
            }
          }
        }
      }
    }
    if ( $errors )
    {
      # We are in trouble, just die....
      mtr_error("we could not kill or clean up all processes");
    }
  }

  stop_reap_all();

  # FIXME We just assume they are all dead, we don't know....
}

sub start_reap_all {
  $SIG{CHLD}= 'IGNORE';                 # FIXME is this enough?
}

sub stop_reap_all {
  $SIG{CHLD}= 'DEFAULT';
}

1;
