#!/bin/sh

# Run commands on CTDB nodes.

# See http://ctdb.samba.org/ for more information about CTDB.

# Copyright (C) Martin Schwenke  2008

# Based on an earlier script by Andrew Tridgell and Ronnie Sahlberg.

# Copyright (C) Andrew Tridgell  2007

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
   
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
   
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

prog=$(basename $0)

usage ()
{
    cat >&2 <<EOF
Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
  options:
    -c         Run in current working directory on all nodes.
    -p         Run command in parallel on specified nodes.  Implies -t.
    -q         Do not print node addresses (overrides -v).
    -v         Print node address even for a single node.
  <NODES>      "all" or a node number (0 base); or
               list (comma separated) of <NODES>; or
               range (hyphen separated) of node numbers.
EOF
    exit 1

}

invalid_nodespec ()
{
    echo "Invalid <nodespec>" >&2 ; echo >&2
    usage
}

# Defaults.
current=false
parallel=false
verbose=false
quiet=false

parse_options ()
{
    # $POSIXLY_CORRECT means that the command passed to onnode can
    # take options and getopt won't reorder things to make them
    # options ot onnode.
    temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "chpqv" -l help -- "$@")

    [ $? != 0 ] && usage

    eval set -- "$temp"

    while true ; do
	case "$1" in
	    -c) current=true ; shift ;;
	    -p) parallel=true ; shift ;;
	    -q) quiet=true ; shift ;;
	    -v) verbose=true ; shift ;;
	    --) shift ; break ;;
	    -h|--help|*) usage ;; # Shouldn't happen, so this is reasonable.
	esac
    done

    [ $# -lt 2 ] && usage

    nodespec="$1" ; shift
    command="$@"
}

# Can probably be avoided if we use bash?
get_nth ()
{
    n="$1" ; shift

    c=0
    for j ; do
	if [ $n -eq $c ] ; then
	    echo $j
	    break
	fi
	c=$(($c + 1))
    done
}

parse_nodespec ()
{
    # Subshell avoids hacks to restore $IFS.
    (
	IFS=","
	for i in $1 ; do
	    case "$i" in
		*-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
		all) echo all ;;
		*)
		    [ $i -gt -1 ] 2>/dev/null || invalid_nodespec
		    echo $i
	    esac
	done
    )
}

get_nodes ()
{
    [ -f "$CTDB_NODES_FILE" ] || CTDB_NODES_FILE=/etc/ctdb/nodes
    all_nodes=$(egrep '^[[:alnum:]]' $CTDB_NODES_FILE)

    nodes=""
    for n in $(parse_nodespec "$1") ; do
	[ $? != 0 ] && exit 1  # Required to catch exit in above subshell.
	case "$n" in
	    all) echo $all_nodes ;;
	    *)	node=$(get_nth $n $all_nodes)
		if [ -n "$node" ] ; then
		    echo $node
		else
		    echo "${prog}: \"node ${n}\" does not exist" >&2
		    exit 1
		fi
	esac
	
    done
}

######################################################################

parse_options "$@"

$current && command="cd $PWD && $command"

SSH_OPTS=
# Could "2>/dev/null || true" but want to see errors from typos in file.
[ -r /etc/ctdb/onnode.conf ] && . /etc/ctdb/onnode.conf
[ -n "$SSH" ] || SSH=ssh
if [ "$SSH" = "ssh" ] ; then
    ssh_opts="-n"
else
    : # rsh? All bets are off!
fi

######################################################################

nodes=$(get_nodes "$nodespec")
[ $? != 0 ] && exit 1   # Required to catch exit in above subshell.

if $quiet ; then
    verbose=false
else
    # If $nodes contains a space or a newline then assume multiple nodes.
    nl="
"
    [ "$nodes" != "${nodes%[ ${nl}]*}" ] && verbose=true
fi

pids=""
trap 'kill -TERM $pids 2>/dev/null' INT TERM
# There's a small race here where the kill can fail if no processes
# have been added to $pids and the script is interrupted.  However,
# the part of the window where it matter is very small.
for n in $nodes ; do
    if $parallel ; then
	if $verbose ; then
	    ($SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" 2>&1 | sed -e "s@^@[$n] @" )&
	else
	    $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" &
	fi
	pids="${pids} $!"
    else
	if $verbose ; then
	    echo >&2 ; echo ">> NODE: $n <<" >&2
	fi

	$SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command"
    fi
done

$parallel && wait
