#!/bin/sh -u
##################################################################
#		R E X E
#		-------
Usage="
#	rexe [options] [machine ...]
#
# Description:
#	Execute commands on all machines or machines that match
#	specified criteria. If no matching criteria is specified
#	all existing machines are used.
#	The UNIX commands to be executed is by default read from
#	stdin unless the -f option is specified which must specify
#	a bourne shell script file.
#
#	All matching criteria like <-column value> or <machine ...>
#	works exactly as the machines command.
#	This command is quite fast though it executes the specified command
#	on multiple machines simultaneously. The -m option can be
#	used to specify how many parallel processes to use, default is
#	10 parallel processes.
#
# Options:
#	-m <max-processes>	: Specify maximum number of parallel procs.
#	-t timeout		: Set timeout per machine in seconds.
#	-f script-file		: Read commands from file.
#	-c command		: Run commands on machines.
#	-column value		: Run on machines where column has value.
#				: Column is a column in the machines command.
#	-help			: Gives this help
# Examples:
#	To reboot all diskless clients do:
#	    echo reboot | rexe -diskless true
#	To show who:s logged in on machines tns1 tns2 tns3 do:
#	    rexe tns1 tns2 tns3
#	    > who
#	    > ^D
"
#
##################################################################
timeout=120		# Maximum time to wait for a rsh to complete.
cleanupCommands=""	# Cleaning up commands.
count=0			# Counter of current background process.
max=10			# Maximum number of background processes.
prog=`basename $0`	# Name of command.
tmpFile=/tmp/rexe.$$	# Temporary file for storing commands to execute.
failFile=/tmp/rexe-failed.$$	# List of hosts that failed.
PAGER=${PAGER:-more}	# Default pager.
command=""

main()
{
    #
    # Init some data.
    #
    machineOptions=""
    scriptFile=""

    #
    # Setup a proper prompting function.
    #
    if echo -n "x" | grep -- -n > /dev/null; then
	prompt() { echo "> \c"; }
    else
    	prompt() { echo -n "> "; }
    fi

    #
    # Catch interrupts.
    #
    trap "cleanup; exit 1" 1 2 3 15

    #
    # Check arguments.
    #
    while [ $# -ne 0 ]
    do
	case $1 in
	-f)
	    test $# -lt 2 && show_usage
	    scriptFile=$2
	    shift
	    ;;
	
	-c)
	    test $# -lt 2 && show_usage
	    command="$2"
	    shift
	    ;;

	-t)
	    test $# -lt 2 && show_usage
	    timeout=$2
	    shift
	    ;;

	-m)
	    test $# -lt 2 && show_usage
	    max=$2
	    shift
	    ;;

	*)
	    machineOptions="$machineOptions $1"
	    ;;
	esac
	shift
    done

    #
    # If neither script file nor command was specified, get data from stdin.
    #
    if [ -z "$scriptFile" -a -z "$command" ]; then
	add_cleanup_command rm -f $tmpFile
	: > $tmpFile
	while prompt; read line
	do
	    echo "$line" >> $tmpFile
	done 
	echo
	scriptFile=$tmpFile
    fi

    #
    # Use command if specified.
    #
    if [ -n "$command" ]; then
	add_cleanup_command rm -f $tmpFile
    	echo "$command" > $tmpFile
	scriptFile=$tmpFile
    fi

    #
    # Mark that the failFile shall be removed upon exit.
    #
    add_cleanup_command rm -f $failFile

    #
    # Start doing it.
    #
    machines -exist true $machineOptions > /tmp/rexe-hosts$$
    (
	while read host
	do
	    (
		log=/tmp/rexe-$count.$$
		err=/tmp/rexe-error-$count.$$
		satimeout -$timeout rsh $host /bin/sh < $scriptFile > $log 2> $err
		if [ $? -ne 0 -o -s $err ]; then
		    echo $host: `cat $err` >> $failFile
		    echo $host:fail:`cat $err`
		else
		    echo $host:ok:`cat $log`
		fi
		rm -f $log $err
	    ) &
	    if [ $count -gt $max ]; then
		count=0
		wait
	    else
		count=`expr $count + 1`
	    fi
	done 
	wait
    ) < /tmp/rexe-hosts$$
    rm -f /tmp/rexe-hosts$$

    #
    # Print which hosts failed.
    #
    if [ -s $failFile ]; then
	echo
	echo "-----------------------"
	echo "following hosts failed:"
	cat $failFile
    fi

    #
    # Do the cleaning up.
    #
    cleanup
}

#
##################################################################
#		C L E A N U P
#		-------------
# Description:
#	Do all necessary cleaning up before exiting
#
##################################################################
cleanup()
{
    echo "$cleanupCommands" | sh
    return 0
}
#
##################################################################
#		A D D _ C L E A N U P _ C O M M A N D
#		-------------------------------------
# Description:
#	Add a command to be executed at cleanup.
#
##################################################################
add_cleanup_command()
{
    cleanupCommands="$*
$cleanupCommands"
}
#
##################################################################
#		S H O W _ U S A G E
#		-------------------
# Description:
#	Print show_usage of command and exit.
#
##################################################################
show_usage()
{
    echo "Usage:$Usage" | sed 's/^# *//' | $PAGER
    exit 1
}


#
# M a i n   c o d e .
#
main "${@-""}"

#
# History of changes:
# rexe,v
# Revision 1.8  1997/10/02 13:16:04  lasse
# Bugfix when running without the -c option
#
# Revision 1.7  1996/09/14 18:33:26  lasse
# Added some things to the TODO and added pargs
#
# Revision 1.6  1996/07/13 19:08:25  lasse
# Various corrections
#
# Revision 1.5  1996/03/21 18:49:01  lasse
# Read hosts from file instead of pipe which may hang on some systems (suns)
#
# Revision 1.4  1996/03/12  19:42:27  lasse
# Checking in from remote.
#
# Revision 1.3  1995/10/28  11:26:26  lasse
# Choose correct echo method (-n or \c)
#
# Revision 1.2  1995/09/23  14:44:27  lasse
# Use satime instead of only timeout
#
# Revision 1.1  1995/09/23  13:54:05  lasse
# Created
#
#
