#!/usr/bin/perl
# @(#)55  1.1  src/43haes/usr/sbin/cluster/cspoc/dsh, powerha, 61aha_r721 10/27/16 08:31:51
# @(#)55  1.1  src/43haes/usr/sbin/cluster/cspoc/dsh, powerha, 61aha_r721 10/27/16 08:31:51
#####################################################################
#                                                                   #
# Module: dsh                                                       #
#                                                                   #
#CPRY                                                               #
# 5765-296 (C) Copyright IBM Corporation 1994                       #
# Licensed Materials - Property of IBM                              #
# All rights reserved.                                              #
# US Government Users Restricted Rights -                           #
# Use, duplication or disclosure restricted by                      #
# GSA ADP Schedule Contract with IBM Corp.                          #
#CPRY                                                               #
#                                                                   #
#-------------------------------------------------------------------#
#                                                                   #
# Description: Provides method for running commands on multiple     #       
#              nodes or network connected hosts in parallel.        #       
#                                                                   #
# Inputs:                                                           #
#  -v : verify hosts before adding to collective                    #
#  -i : inform of working collective before command execution       #
#  -q : list the working collective for command execution           #
#  -l : specify login name to execute remote commands               #
#  -h : display dsh command syntax                                  #
#  -c : specify that commands will continue to be sent to           #
#       hosts for which previous commands have failed               #
#  -a : add all members of current SysPar to working collective     # 
#  -G : extend scope of -a to all members of SP System              # 
#  -w : add list of hostnames to working collective                 #
#  -f : fanout for concurrent execution                             #
#                                                                   #
# Ouputs:                                                           #
#	Commands executed on hosts in working collective            #    
#                                                                   #
# Syntax (example):                                                 #
#       dsh -w host1,host2 -vi ls                                   #
#                                                                   #
# External Ref:                                                     #
#	SDRGetObjects                                               #    
#                                                                   #
# Internal Ref: None                                                #
#                                                                   #
#####################################################################

#-----------------------------------------------------------------------------
#
# do_msg    
#
# Issue message
#
#-----------------------------------------------------------------------------

sub do_msg {

   local($num,$arg1,$arg2,$arg3,$prog) = @_;
   local($MSGCAT,$SPMSG);

   $MSGCAT = "$msgdir/\?/cl_smsysman.cat";
   $SPMSG = "dspmsg";
   chop($prog = `/bin/basename $0`);

   if ($num == 1) {
      system "$SPMSG -s 2 $MSGCAT 10 'dsh:  5025-501 Msg EMSG501 not found. No alphanumeric characters, command not issued.\n' $prog";
   }
   elsif ($num == 2) {
      system "$SPMSG -s 2 $MSGCAT 1 'Msg INFO501 not found. Usage:
\t-v : verify hosts before adding to collective
\t-i : verify collective before doing commands
\t-l login_name : remote login name
\t-f number : number to fanout for concurrent execution
\t-q : list the working collective for command execution
\t-h : display DSH command syntax
\t-c : commands continue to be sent to hosts for which commands failed
\t-a : add all members of current System Partition to working collective
\t-G : extend scope of -a to all members of SP System
\t-w host,host : add list of hostnames to working collective\n'";
   }
   elsif ($num == 3) {
     system "$SPMSG -s 2 $MSGCAT 2 'Msg INFO502 not found. %1\$s not responding.  Enter y if add to collective\n' $arg1 1>&2";
   }
   elsif ($num == 4) {
      system "$SPMSG -s 2 $MSGCAT 3 'Msg INFO503 not found. Working collective file %1\$s:\n' $arg1";
   }
   elsif ($num == 5) {
      system "$SPMSG -s 2 $MSGCAT 11 'dsh:  5025-502 Msg EMSG502 not found. Extraneous option - %1\$s\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 6) {
      system "$SPMSG -s 2 $MSGCAT 12 'dsh:  5025-503 Msg EMSG503 not found. Invalid argument - %1\$s\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 7) {
      system "$SPMSG -s 2 $MSGCAT 13 'dsh:  5025-504 Msg EMSG504 not found. Missing argument\n' $prog 1>&2";
   }
   elsif ($num == 8) {
      system "$SPMSG -s 2 $MSGCAT 14 'dsh:  5025-505 Msg EMSG505 not found. Invalid option - %1\$s\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 9) {
      system "$SPMSG -s 2 $MSGCAT 15 'dsh:  5025-506 Msg EMSG506 not found. Missing option\n' $prog 1>&2";
   }
   elsif ($num == 10) {
      system "$SPMSG -s 2 $MSGCAT 4 'Msg INFO504 not found. Working collective:\n' 1>&2";
   }
   elsif ($num == 11) {
      system "$SPMSG -s 2 $MSGCAT 16 'dsh:  5025-507 Msg EMSG507 not found. Working collective environment variable not set\n' $prog 1>&2";
   }
   elsif ($num == 12) {
      system "$SPMSG -s 2 $MSGCAT 17 'dsh:  5025-508 Msg EMSG508 not found. Cannot open working collective file %1\$s: %2\$s\n' $arg1 $arg2 $prog 1>&2";
   }
   elsif ($num == 13) {
      system "dspmsg scripts.cat 9682 '%1\$s: cl_rsh had exit code = %2\$s, see cspoc.log and/or clcomd.log for more information\n' $arg1 $arg2 1>&2";
   }
   elsif ($num == 14) {
      system "$SPMSG -s 2 $MSGCAT 19 'dsh:  5025-510 Msg EMSG510 not found. Caught SIG%1\$s - terminating the child processes\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 15) {
      system "$SPMSG -s 2 $MSGCAT 20 'dsh:  5025-511 Msg EMSG511 not found. No hosts in working collective.\n' $prog 1>&2";
   }
   elsif ($num == 17) {
      system "$SPMSG -s 2 $MSGCAT 21 'dsh:  5025-512 Msg EMSG512 not found. Could not pipe\n' $prog 1>&2";
   }
   elsif ($num == 18) {
      system "$SPMSG -s 2 $MSGCAT 22 'dsh:  5025-513 Msg EMSG513 not found. Cannot redirect %1\$s\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 19) {
      system "$SPMSG -s 2 $MSGCAT 6 'Msg INFO506 not found. Verifying host %1\$s...\n' $arg1 1>&2";
   }
   elsif ($num == 20) {
      system "$SPMSG -s 2 $MSGCAT 7 'Msg INFO507 not found. Fanout: %1\$s\n' $arg1 1>&2";
   }
   elsif ($num == 21) {
      system "$SPMSG -s 2 $MSGCAT 23 'dsh:  5025-514 Msg EMSG514 not found. System data repository down\n' $prog 1>&2";
   }
   elsif ($num == 22) {
      system "$SPMSG -s 2 $MSGCAT 24 'dsh:  5025-515 Msg EMSG515 not found. Hostname %1\$s unresolvable\n' $arg1 $prog 1>&2";
   }
   elsif ($num == 23) {
      system "$SPMSG -s 2 $MSGCAT 8 'Msg INFO508 not found. Fanout: 64\n' 1>&2";
  }
}

#-----------------------------------------------------------------------------
#
# read_pipes
#
# Read stdout and stderr from exiting child rsh process pipes.
# Display to parent's stdout or stderr preceded by "hostname: ".
#
# Input is name of host for which rsh child is exiting.
#
#-----------------------------------------------------------------------------

sub read_pipes {

   local($hostn) = @_;
   local($ofh,$efh);
   $ofh = $r_out{$hostn};
   while (<$ofh>) {
     print STDOUT "$hostn: $_";
   } 
   close($ofh);
   $efh = $r_err{$hostn};
   while (<$efh>) {
     print STDERR "$hostn: $_";
   }
   close($efh);
}
   
#-----------------------------------------------------------------------------
#
# get_command
#
# Return the command from the command line or stdin.
# Return 0 if no more to be read from command line or stdin.
# If the command starts with '!', execute here and read in the next command.
#
#-----------------------------------------------------------------------------

sub get_command {

   local($command);
   if ($done) {
      return(0);
   }
   if (@ARGV) {
      $done = 1;
      @dsh_ARGV = @ARGV;
      shift(@ARGV);
      return(@dsh_ARGV);
   }
   GET_COMMAND: { 
   -t && print STDERR "dsh> ";
   $command = <STDIN>;
   if (!defined($command) || $command =~ /^\s*$/ || $command =~ /^\s*exit\s*$/) {
      return(0);
   }
   else {
      chop $command;
      if ($command =~ /^\s*!(.*)/) {
         &do_system($1);
         redo GET_COMMAND;
      }
      else {
         return($command) || redo GET_COMMAND;
      }
   }
   }
}

#-----------------------------------------------------------------------------
#
# do_system
#
# Issue system() call with default signal handling.
# Return the executed command's exit code.
#
#-----------------------------------------------------------------------------

sub do_system {

   local($command) = @_;
   local(%save_sig,$rc);
   %save_sig = %SIG;
   grep($_ = 'DEFAULT', %SIG);
   $rc = system("$command 2>&1") >> 8;
   %SIG = %save_sig;
   return($rc);
}

#-----------------------------------------------------------------------------
#
# d_syntax
#
# Display help info
#
#-----------------------------------------------------------------------------

sub d_syntax {

&do_msg(2,'','','');
}

#-----------------------------------------------------------------------------
#
# check_SDR
#
# Return 1 if the SDR is up, 0 if it is down. If already checked, return
# previous result (only want to check it once per invocation).
#
#-----------------------------------------------------------------------------

sub check_SDR {
   
   if (!$checked_SDR) {
      $checked_SDR++;
      if (&do_system("$bindir/SDRGetObjects SP > /dev/null 2>&1")) {
         $prev_result = 0;
      }
      else {
         $prev_result = 1;
      }
   }
   return($prev_result);
}

#-----------------------------------------------------------------------------
#
# add_wc
#
# Add a host to the working collective. If the -v flag is set, check if the
# host is responding first. Try the SDR, if not there, try ping.
# Don't add a hostname if it is already there.
# Input is the hostname to add.
#
#-----------------------------------------------------------------------------

sub add_wc {

   local($host) = @_;
   local($hostname,@hostnames);
   local($name,$aliases,$addtype,$length,@addrs);
   $host =~ s/\s//g;
   if ($verify) {
      #print STDERR "Verifying $host...\n";
      $iflag && &do_msg(19,$host,'','');
      %is_responding || &build_resp_array;
      ($name,$aliases,$addtype,$length,@addrs) = gethostbyname($host);
      unless ($name) {
         #print STDERR "Hostname $host unresolvable\n";
         &do_msg(22,$host,'','');
         return;
      }
      if ($is_responding{$name} eq "0") {
         &include_anyway($host) || return;
      }
      elsif ($is_responding{$name} ne "1") {
         if (&do_system("/etc/ping -c 1 $host > /dev/null 2>&1")) {
            &include_anyway($host) || return;
         } 
      }
   }
   foreach $hostname (@wc) {
      return if $hostname eq $host;
   }
   push(@wc,$host);
}

#-----------------------------------------------------------------------------
#
# include_anyway
#
# Inform of not responding node and prompt if to include in collective.
# If include it, return 1, else 0.
#
#-----------------------------------------------------------------------------

sub include_anyway {

   local($host) = @_;
   local($ans);          
   $iflag || return (0);
   #print STDERR $host," not responding.  Enter y if add to collective\n";
   &do_msg(3,$host,'','');
   open(TERM,"/dev/tty");
   chop($ans = <TERM>);
   return ($ans =~ /^\s*[Yy]\s*$/);
}

#-----------------------------------------------------------------------------
#
# build_resp_array
#
# Build an array indicating whether hosts are responding, based on the SDR. 
#
#-----------------------------------------------------------------------------

sub build_resp_array {

   local(@nodelines,@resplines,%r_hostname,%responding,$r,$noden,$rhn);
   &check_SDR || return;
      @nodelines = `$bindir/SDRGetObjects -G Node node_number initial_hostname`;
   shift @nodelines;
   chop @nodelines;
   chop @nodelines;
   foreach (@nodelines) {
      ($discard,$noden,$rhn) = split(/\s+/,$_);
      $rhn =~ s/ //g;
      $r_hostname{$noden} = $rhn;
  }
   @resplines = `$bindir/SDRGetObjects -G host_responds`;
   shift @resplines;
   chop @resplines;
   chop @resplines;
   foreach (@resplines) {
      ($discard,$noden,$r) = split(/\s+/,$_);
      $r =~ s/ //g;
      $responding{$noden} = $r;
  }
   foreach (keys %r_hostname) {
      $is_responding{$r_hostname{$_}} = $responding{$_};
   }
}

#-----------------------------------------------------------------------------
#
# add_cluster_wc
#
# Get node objects currently in SDR for current System Partition with 
# hostname attributes and add to the working collective.
#
#-----------------------------------------------------------------------------

sub add_cluster_wc {

   local($hostname,@hostnames);
   unless (&check_SDR) {
      #die "System data repository down\";
      &do_msg(21,'','','');
      exit(-1);
   }
   @hostnames = `$bindir/SDRGetObjects $global_opt Node initial_hostname`;
   shift @hostnames;
   chop @hostnames;
   chop @hostnames;
   foreach $hostname (@hostnames) {
      &add_wc($hostname);
   }
}

#-----------------------------------------------------------------------------
#
# parse
#
# Parse the command line
#
#-----------------------------------------------------------------------------

sub parse {

   local(@indices,@temp,$rest,$findex,$windex,$lindex,$wcf,$wcoll_file,$host,@hostlist,$ht,$hl);
   if ($ARGV[0] =~ /^-[hq](\S+)/) { 
      #die "dsh: Extraneous option - $1\n";
      &do_msg(5,$1,'','');
      &d_syntax;
      exit(-1);
   }
   if ($ARGV[0] =~ /^-q$/) {
      if (!defined $ARGV[1]) {
         &get_wc;
         #print STDOUT "Working collective file $ENV{'WCOLL'}:\n";
         &do_msg(4,$ENV{'WCOLL'},'','');
         print STDERR join("\n",@wc),"\n";
         if (!($fanout = $ENV{'FANOUT'})) {
            #print STDOUT "Fanout: 64\n";
            &do_msg(23,'','',''); 
            exit(0);
         }
         #print STDOUT "Fanout: $fanout\n";
         &do_msg(20,$fanout,'',''); 
         exit(0);
      }
      else {
         #die "dsh: Extraneous option - $ARGV[1]\n";
         &do_msg(5,$ARGV[1],'','');
         &d_syntax;
         exit(-1);
      }
   }
   if ($ARGV[0] =~ /^-h$/) {
      if (!defined $ARGV[1]) {
         &d_syntax;
         exit(0);
      }
      else {
         #die "dsh: Extraneous option - $ARGV[1]\n";
         &do_msg(5,$ARGV[1],'','');
         &d_syntax;
         exit(-1);
      }
   }
   while ($ARGV[0] =~ /^-/) {
      if ($ARGV[0] =~ /[wfl](\S+)/) {
         $findex = index($ARGV[0],"f");
         $windex = index($ARGV[0],"w");
         $lindex = index($ARGV[0],"l");
         @indices = ($findex, $windex, $lindex);
         @indices = sort @indices;
         @temp = @indices;
         foreach (@temp) {
            $_ == -1 && shift(@indices);
         }
         if ($indices[0] == $findex) {
            if (!$fanout) {
               $fanout = $1;
               if ($fanout =~ /\D/) {
                  #die "dsh: Invalid argument - $fanout\n";
                  &do_msg(6,$fanout,'','');
                  &d_syntax;
                  exit(-1);
               }
            }
            else {
               #die "dsh: Extraneous option - f\n";
               &do_msg(5,"f",'','');
               &d_syntax;
               exit(-1);
            }
         }
         elsif ($indices[0] == $windex) {
            if (!$hl) {
               $hl = $1;
               if ($hl =~ /^,|,,|,$/) {
                  #die "Invalid argument - $hl\n";
                  &do_msg(6,$hl,'','');
                  &d_syntax;
                  exit(-1);
               }
               if ($hl eq "-") {
                  while (<STDIN>) {
                     /^\s*#/ && next;
                     /^\s*$/ && next;
                     /;/ && next;
                     /\S+ \S+/ && next;
                     s/ //g;
                     chop;
                     push(@hostlist,$_);
                  }
               }
               else {		  
                  @hostlist = split(/,/,$hl);
	       }
            }
            else {
               #die "dsh: Extraneous option - w\n";
               &do_msg(5,"w",'','');
               &d_syntax;
               exit(-1);
            }
         }
         elsif ($indices[0] == $lindex) {
            if (!$login) {
               $login = $1;
            }
            else {
               #die "dsh: Extraneous option - l\n";
               &do_msg(5,"l",'','');
               &d_syntax;
               exit(-1);
            }
         }
         $ARGV[0] = substr($ARGV[0], 0, $indices[0] + 1);
      }
      elsif ($ARGV[0] =~ /f$/) {
         if (!$fanout) {
            $fanout = $ARGV[1];
            unless ($fanout) {
               #die "dsh: Missing argument\n";
               &do_msg(7,'','','');
               &d_syntax;
               exit(-1);
            }
            if ($fanout =~ /\D/) {
               #die "dsh: Invalid argument - $fanout\n";
               &do_msg(6,$fanout,'','');
               &d_syntax;
               exit(-1);
            }
            $shiftflag++; 
         }
         else {
            #die "dsh: Extraneous option - f\n";
            &do_msg(5,"f",'','');
            &d_syntax;
            exit(-1);
         }
      }
      elsif ($ARGV[0] =~ /l$/) {
         if (!$login) {
            $login = $ARGV[1];
            unless ($login) {
               #die "dsh: Missing argument\n";
               &do_msg(7,'','','');
               &d_syntax;
               exit(-1);
            }
            $shiftflag++; 
         }
         else {
            #die "dsh: Extraneous option - l\n";
            &do_msg(5,"l",'','');
            &d_syntax;
            exit(-1);
         }
      }
      elsif ($ARGV[0] =~ /w$/) {
         if (!$hl) {
            $hl = $ARGV[1];
            unless ($hl) {
              #die "dsh: Missing argument\n";
               &do_msg(7,'','','');
               &d_syntax;
               exit(-1);
            }
            if ($hl =~ /^,|,,|,$/) {
               #die "Invalid argument - $hl\n";
               &do_msg(6,$hl,'','');
               &d_syntax;
               exit(-1);
            }
            if ($hl eq "-") {
               while (<STDIN>) {
                  /^\s*#/ && next;
                  /^\s*$/ && next;
                  /;/ && next;
                  /\S+ \S+/ && next;
                  s/ //g;
                  chop;
                  push(@hostlist,$_);
               }
            }
            else {		  
               @hostlist = split(/,/,$hl);
            }
            $shiftflag++; 
         }
         else {
            #die "dsh: Extraneous option - w\n";
            &do_msg(5,"w",'','');
            &d_syntax;
            exit(-1);
         }
      }
      if ($ARGV[0] =~ /^-.*([^iaGcvwfl]).*/) {
         #die "dsh: Invalid option - $1\n";
         &do_msg(8,$1,'','');
         &d_syntax;
         exit(-1);
      }
      if ($ARGV[0] =~ /^-$/) {
         #die "dsh: Missing option\n";
         &do_msg(9,'','','');
         &d_syntax;
         exit(-1);
      }
      if (index($ARGV[0], 'i') >= $[) {
         if (rindex($ARGV[0], 'i') == index($ARGV[0], 'i')) {
            if ($iflag++) {
               #die "dsh: Extraneous option - i\n";
               &do_msg(5,"i",'','');
               &d_syntax;
               exit(-1); 
            }
         }
         else {
            #die "dsh: Extraneous option - i\n";
            &do_msg(5,"i",'','');
            &d_syntax;
            exit(-1); 
         } 
      }         
      if (index($ARGV[0], 'v') >= $[) {
         if (rindex($ARGV[0], 'v') == index($ARGV[0], 'v')) {
            if ($verify++) {
               #die "dsh: Extraneous option - v\n";
               &do_msg(5,"v",'','');
               &d_syntax;
               exit(-1); 
            }
         }
         else {
            #die "dsh: Extraneous option - v\n";
            &do_msg(5,"v",'','');
            &d_syntax;
            exit(-1); 
         }
      } 
      if (index($ARGV[0], 'G') >= $[) {
         if (rindex($ARGV[0], 'G') == index($ARGV[0], 'G')) {
            if ($Gflag++) {
               #die "dsh: Extraneous option - G\n";
               &do_msg(5,"G",'','');
               &d_syntax;
               exit(-1); 
            }
            $global_opt = "-G";
         }
         else {
            #die "dsh: Extraneous option - G\n";
            &do_msg(5,"G",'','');
            &d_syntax;
            exit(-1); 
         }
      }         
      if (index($ARGV[0], 'a') >= $[) {
         if (rindex($ARGV[0], 'a') == index($ARGV[0], 'a')) {
            if ($aflag++) {
               #die "dsh: Extraneous option - a\n";
               &do_msg(5,"a",'','');
               &d_syntax;
               exit(-1); 
            }
            $wfound++;
         }
         else {
            #die "dsh: Extraneous option - a\n";
            &do_msg(5,"a",'','');
            &d_syntax;
            exit(-1); 
         }
      }         
      if (index($ARGV[0], 'c') >= $[) {
         if (rindex($ARGV[0], 'c') == index($ARGV[0], 'c')) {
            if ($continue++) {
               #die "dsh: Extraneous option - c\n";
               &do_msg(5,"c",'','');
               &d_syntax;
               exit(-1); 
            }
         }
         else {
            #die "dsh: Extraneous option - c\n";
            &do_msg(5,"c",'','');
            &d_syntax;
            exit(-1); 
         }
      }         
      shift(@ARGV);
      if ($shiftflag) {
         shift(@ARGV);
         $shiftflag--;
      }
   }
   $aflag && &add_cluster_wc;
   if (@hostlist) {
      $wfound++;
      foreach $ht (@hostlist) {
         &add_wc($ht);
      }
   }
}

#-----------------------------------------------------------------------------
#
# set_defaults
# 
# Set default values, if any. 64 is default fanout.
#
#-----------------------------------------------------------------------------

sub set_defaults {

   unless ($fanout) {  
      unless ($fanout = $ENV{'FANOUT'}) {
         $fanout = 64;
      }
   }
   unless ($login) {
      $login = (getpwuid($<))[0];
   }
}

#-----------------------------------------------------------------------------
#
# readem
# 
# Read the stdout and stderr pipes for all hosts in current fanout.
#
#-----------------------------------------------------------------------------

sub readem {

   local(@h) = @_;
   local($host);
   local($host_reader);
   foreach $host (@h) {
      $host_reader=$host . "_reader";
      if($pid{$host_reader} = fork) {
	next;
      }
      else {
	&read_pipes($host);
	exit(0);
      }
   }
   @h = ();
}

#-----------------------------------------------------------------------------
#
# display_wc
#
# Display the working collective, if requested with the -i option.
#
#-----------------------------------------------------------------------------

sub display_wc {

   local($i,$ans);
   if ($iflag) {
      #print STDOUT "Working collective:\n";
      &do_msg(10,'','','');
      $i = 0;
      while ($i <= $#wc) {
         printf STDERR "%-19.18s", $wc[$i]; 
         printf STDERR "%-19.18s", $wc[$i+1];
         printf STDERR "%-19.18s", $wc[$i+2];
         printf STDERR "%-19.18s\n", $wc[$i+3];
         $i = $i + 4;
      }
   }
}


#-----------------------------------------------------------------------------
#
# get_wc
#
# Determine the working collective, if not already obtained from command line.
# Look for filename in $WCOLL containing the hostnames, one per line.
#
#-----------------------------------------------------------------------------

sub get_wc {

   local($wfile,$new_host);
   if (!@wc && !$wfound) {
      unless ($wfile = $ENV{'WCOLL'}) {
         #die "dsh: Working collective environment variable not set\n";
         &do_msg(11,'','','');
         exit(-1);
      }
      unless (open(WCFILE, $wfile)) { 
         #die "dsh: Cannot open working collective file $wfile: $!\n";
         &do_msg(12,$wfile,"\'$!\'",'');
         exit(-1);
      }
      while ($new_host = <WCFILE>) {
         $new_host =~ /^\s*#/ && next;
         $new_host =~ /^\s*$/ && next;
         $new_host =~ /;/ && next;
         $new_host =~ /\S+\s+\S+/ && next;
         chop($new_host);
         &add_wc($new_host);
      }
      close(WCFILE);
   }
}

#-----------------------------------------------------------------------------
#
# set_signals      
#
# HUP is ignored in the dsh parent and exec'ed rsh children.
#
# STOP, CONT, and TSTP are defaulted - this means that they work on the
# parent, but are ignored (not propagated to) the exec'ed rsh children
# or the remote processes.
#
# Set the signal handler for all other signals.
# The signals will be propagated to the execed children and then the
# default action will be taken in the parent.
#
# Rsh will propagate TERM, QUIT, and INT to the remote processes.
#
#-----------------------------------------------------------------------------

sub set_signals {

   # Default STOP, CONT, TSTP signal handling

   $SIG{'STOP'} = 'DEFAULT';
   $SIG{'CONT'} = 'DEFAULT';
   $SIG{'TSTP'} = 'DEFAULT';

   # Propagate signals to forked kids

   $SIG{'TERM'} = 'infanticide';
   $SIG{'QUIT'} = 'infanticide';
   $SIG{'INT'} = 'infanticide';
   $SIG{'ABRT'} = 'infanticide';
   $SIG{'ALRM'} = 'infanticide';
   $SIG{'FPE'} = 'infanticide';
   $SIG{'ILL'} = 'infanticide';
   $SIG{'PIPE'} = 'infanticide';
   $SIG{'SEGV'} = 'infanticide';
   $SIG{'USR1'} = 'infanticide';
   $SIG{'USR2'} = 'infanticide';
   $SIG{'TTIN'} = 'infanticide';
   $SIG{'TTOU'} = 'infanticide';
   $SIG{'BUS'} = 'infanticide';

}
 
#-----------------------------------------------------------------------------
#
# wait_for_kids
#
# When a child dies, it must be an exit after the end of his rsh.
# If a negative return code, the rsh itself failed. Check to see if we
# should give up on this host and eliminate him from the collective.
# Display the contents of his stdout/stderr pipes and save the rsh exit code
# in an environment variable.
#
#-----------------------------------------------------------------------------

sub wait_for_kids {

   local($child_pid,$child_rc,$child_host);
   while (($child_pid = wait) != -1) {
      $child_rc = $? >> 8;
      foreach (keys %pid) {
         if ($pid{$_} == $child_pid) {
            $child_host = $_;
            last;
         }
      }
      if ($child_rc != 0) {
         $rcode++;
         #print STDERR "dsh: $child_host rsh had exit code $child_rc\n";
         &do_msg(13,$child_host,$child_rc,'');
         push(@goners,$child_host);
      }
#      &read_pipes($child_host);
   }
}

#-----------------------------------------------------------------------------
#
# delete_hosts
#
# Called if any hosts don't respond. Remove them from the working collective 
# unless the -c flag was set. 
# Input is the hostnames to remove from the working collective.
#
#-----------------------------------------------------------------------------

sub delete_hosts {

   local($child_host,$h,$host_count);
   unless ($continue) {
      foreach $child_host (@goners) {
         $host_count = 0;
         foreach $h (@wc) {
            if ($h eq $child_host) {
               splice(@wc, $host_count, 1); 
            } 
            $host_count++;
         }
      }
   }
}

#-----------------------------------------------------------------------------
#
# infanticide
# 
# User has signaled the dsh parent - propagate TERM, INT, or QUIT to children.
# (Note - TERM, INT, and QUIT will be propagated to remote processes by rsh).
# Signal any children with SIGTERM if signal is not one of the above.
# Wait for children to manage output and prevent zombies.
# Signal self after setting default signal-handling for self.
# If still alive, exit.
# Input is the signal type.
#   
#-----------------------------------------------------------------------------

sub infanticide {

   local($sig) = @_;
   local($kid_sig);
   #print STDERR "dsh: Caught SIG$sig - terminating the kids\n";
   &do_msg(14,$sig,'','');
   if ($sig ne 'QUIT' && $sig ne 'INT' && $sig ne 'TERM') {
      $kid_sig = 'TERM';
      $SIG{'TERM'} = 'IGNORE';
   }
   else {
      $kid_sig = $sig;
   }
   $SIG{$sig} = 'DEFAULT';
   kill $kid_sig, %pid;
   &readem(@hs);
   &wait_for_kids;
   kill $sig, $$;
   exit($rcode);
}

#------------------------------------------------------------------------------
#
# check_wc
#
# Check to ensure that there are hosts in the collective
#
#------------------------------------------------------------------------------

sub check_wc {

   if (!@wc) {
      #print STDERR "dsh: No hosts in working collective.\n";
      &do_msg(15,'','','');
      exit($rcode++);
   }
}

#------------------------------------------------------------------------------
#
# Main line program 
#
# Program continues while there are commands to distribute to working
# collective via rsh. Children are forked for each host (up to the fanout
# limit). Stdout and stderr are piped back from the children to the parent
# who will display the results of the execed rsh's to the parent's stdout
# and stderr.
#
#------------------------------------------------------------------------------

# Path for our executables

$msgdir = "/usr/lib/nls/msg";
$bindir = "/usr/es/sbin/cluster/cspoc";

$global_opt = "";				# Supplied to SDRGetObjects

# Flush output filesystem buffers after each write
select(STDOUT); $| = 1;
select(STDERR); $| = 1;

# Set signal handling, parse the command line, and determine the working
# collective 

&parse;
&get_wc;
&display_wc;
&set_defaults;
&set_signals;
&check_wc;

# Perform each command on the working collective

while ($command = &get_command) {

   $beenhere++ || &check_wc;

   if ($iflag) { 
      if (@dsh_ARGV) {
         print STDERR (">>> ",join(' ',@dsh_ARGV),"\n");
      }
      else {
         $print_command = `echo $command`;
         print STDERR ">>> $print_command";
      }
   }

   foreach $host (@wc) {

      # Create filehandles for pipe ends

      $r_out{$host} = "READ_STDOUT_" . $host;
      $w_out{$host} = "WRITE_STDOUT_" . $host;
      $r_err{$host} = "READ_STDERR_" . $host;
      $w_err{$host} = "WRITE_STDERR_" . $host;

      # Open pipes for this host's stdout and stderr from rsh

      if (pipe($r_out{$host}, $w_out{$host}) < 0) {
         #die "dsh: Couldn't pipe: $!\n";
         &do_msg(17,"\'$!\'",'','');
         exit(-1);
      }
      if (pipe($r_err{$host}, $w_err{$host}) < 0) {
         #die "dsh: Couldn't pipe: $!\n";
         &do_msg(17,"\'$!\'",'','');
         exit(-1);
      }

      # Fork a child to exec the rsh

      FORK: {

      if ($pid{$host} = fork) {
        
         # parent code - 
         # close unneeded ends of pipes 
         # parent will wait for child processes if fanout limit reached
      
         close($w_out{$host});
         close($w_err{$host});
 
         push(@hs,$host);

         if (++$cur_fanout >= $fanout) {
            $cur_fanout = 0;
            &readem(@hs);
            &wait_for_kids;
         }
      } 

      elsif (defined $pid{$host}) {
         
         # child code - 
         # close unneeded ends of pipes
         # redirect stdout and stderr to output pipes
         # exec rsh
         # stdout/stderr will go to pipes to be read by parent
      
         close($r_out{$host});
         close($r_err{$host});

         unless (open(STDOUT, ">&$w_out{$host}")) {
            #die "dsh: Cannot redirect STDOUT: $!\n";
            &do_msg(18,'STDOUT',"\'$!\'",'');
            exit(-1);
         }
         unless (open(STDERR, ">&$w_err{$host}")) {
            #die "dsh: Cannot redirect STDERR: $!\n";
            &do_msg(18,'STDERR',"\'$!\'",'');
            exit(-1);
         }

         select(STDOUT); $| = 1;
         select(STDERR); $| = 1;

         if (!@dsh_ARGV) {    
               exec "/usr/es/sbin/cluster/utilities/cl_rsh $host $command" || die "dsh:rsh: $host $command: $!\n";
         }
         else {
               exec ('/usr/es/sbin/cluster/utilities/cl_rsh',$host,@dsh_ARGV) || die "dsh:rsh: $host $command: $!\n";
         }
      } 
      else { 

        # try again fork must have failed due to resource problem

        sleep 5;
        redo FORK;
      }
      }
   }
   
   # parent continues here after forking all children for this command
   # get the results of any remaining rsh's, if any
   # (number of hosts in working collective may not be multiple of fanout)
   # get rid of any hosts that have failed
   # display working collective and command

   &readem(@hs);
   &wait_for_kids;
   &delete_hosts;
   unless ($done) {
      $cur_fanout = 0;
      &check_wc;
      &display_wc;
   }
}

# Exit

exit($rcode);
