ctdb-tools: Whitespace fixups
[amitay/samba.git] / ctdb / tools / onnode
index 91d8ee815e57788fdde447810a4061927fd16137..95e67d2c0aab65149888949f933d4984f5a06888 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 # Run commands on CTDB nodes.
 
 # 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)
+prog=$(basename "$0")
 
 usage ()
 {
@@ -31,13 +31,16 @@ usage ()
 Usage: onnode [OPTION] ... <NODES> <COMMAND> ...
   options:
     -c          Run in current working directory on specified nodes.
-    -o <prefix> Save standard output from each node to file <prefix>.<ip>
+    -f          Specify nodes file, overriding default.
+    -i          Keep standard input open - the default is to close it.
+    -n          Allow nodes to be specified by name.
     -p          Run command in parallel on specified nodes.
+    -P          Push given files to nodes instead of running commands.
     -q          Do not print node addresses (overrides -v).
     -v          Print node address even for a single node.
-  <NODES>       "all", "ok" (or "healthy"), "con" (or "connected"),
-                "rm" (or "recmaster");
-                or a node number (0 base); or
+  <NODES>       "all", "any", "ok" (or "healthy"), "con" (or "connected") ; or
+                a node number (0 base); or
+                a hostname (if -n is specified); or
                 list (comma separated) of <NODES>; or
                 range (hyphen separated) of node numbers.
 EOF
@@ -53,61 +56,58 @@ invalid_nodespec ()
 
 # Defaults.
 current=false
+ctdb_nodes_file=""
 parallel=false
 verbose=false
 quiet=false
-prefix=""
+names_ok=false
+push=false
+stdin=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.
-    local temp=$(POSIXLY_CORRECT=1 getopt -n "$prog" -o "cho:pqv" -l help -- "$@")
-
-    [ $? != 0 ] && usage
-
-    eval set -- "$temp"
-
-    while true ; do
-       case "$1" in
-           -c) current=true ; shift ;;
-           -o) prefix="$2" ; shift 2 ;;
-           -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="$@"
-}
+if [ -z "$CTDB_BASE" ] ; then
+    CTDB_BASE="/usr/local/etc/ctdb"
+fi
 
-# Can probably be avoided if we use bash?
-get_nth ()
+parse_options ()
 {
-    local n="$1" ; shift
+       local opt
+
+       while getopts "cf:hnpqvPi?" opt ; do
+               case "$opt" in
+               c) current=true ;;
+               f) ctdb_nodes_file="$OPTARG" ;;
+               n) names_ok=true ;;
+               p) parallel=true ;;
+               q) quiet=true ;;
+               v) verbose=true ;;
+               P) push=true ;;
+               i) stdin=true ;;
+               \?|h) usage ;;
+               esac
+       done
+       shift $((OPTIND - 1))
 
-    local c=0
-    local j
-    for j ; do
-       if [ $n -eq $c ] ; then
-           echo $j
-           break
+       if [ $# -lt 2 ] ; then
+               usage
        fi
-       c=$(($c + 1))
-    done
+
+       nodespec="$1" ; shift
+       command="$*"
 }
 
 echo_nth ()
 {
-    local node=$(get_nth "$@")
-    if [ -n "$node" ] ; then
-       echo $node
+    local n="$1" ; shift
+
+    # Note that this is 0-based
+    local node=""
+    if [ "$n" -le $# ] ; then
+           shift "$n"
+           node="$1"
+    fi
+
+    if [ -n "$node" ] && [ "$node" != "#DEAD" ] ; then
+       echo "$node"
     else
        echo "${prog}: \"node ${n}\" does not exist" >&2
        exit 1
@@ -122,153 +122,175 @@ parse_nodespec ()
        for i in $1 ; do
            case "$i" in
                *-*) seq "${i%-*}" "${i#*-}" 2>/dev/null || invalid_nodespec ;;
-               all|ok|healthy|con|connected|rm|recmaster) echo "$i" ;;
+               all|any|ok|healthy|con|connected) echo "$i" ;;
                *)
-                   [ $i -gt -1 ] 2>/dev/null || invalid_nodespec
-                   echo $i
+                   [ "$i" -gt -1 ] 2>/dev/null || $names_ok || invalid_nodespec
+                   echo "$i"
            esac
        done
     )
 }
 
-# Cache
-ctdb_status_output=""
+ctdb_status_output="" # cache
 get_nodes_with_status ()
 {
     local all_nodes="$1"
     local status="$2"
 
-    local bits
-    case "$status" in
-       ok|healthy)
-           bits="0:0:0:0"
-           ;;
-       con|connected)
-           bits="0:[0-1]:[0-1]:[0-1]"
-           ;;
-       *)
-           invalid_nodespec
-    esac
-
     if [ -z "$ctdb_status_output" ] ; then
-       # FIXME: need to do something if $CTDB_NODES_SOCKETS is set.
-       ctdb_status_output=$(ctdb -Y status 2>/dev/null)
+       ctdb_status_output=$(ctdb -X status 2>&1)
+       # No! Checking the exit code afterwards is actually clearer...
+       # shellcheck disable=SC2181
        if [ $? -ne 0 ] ; then
            echo "${prog}: unable to get status of CTDB nodes" >&2
+           echo "$ctdb_status_output" >&2
            exit 1
        fi
-       ctdb_status_output="${ctdb_status_output#* }"
+       local nl="
+"
+       ctdb_status_output="${ctdb_status_output#*${nl}}"
     fi
 
-    local nodes=""
-    local i
-    for i in $ctdb_status_output ; do
-       # Try removing bits from end.
-       local t="${i%:${bits}:}"
-       if [ "$t" != "$i" ] ; then
-           # Succeeded.  Get address.  NOTE: this is an optimisation.
-           # It might be better to get the node number and then use
-           # get_nth() to get the address.  This would make things
-           # more consistent if /etc/ctdb/nodes actually contained
-           # hostnames.
-           nodes="${nodes} ${t##*:}"
-       fi
-    done
+    (
+       local i
+       IFS="${IFS}|"
+       while IFS="" read i ; do
+
+           # Intentional word splitting
+           # shellcheck disable=SC2086
+           set -- $i # split line on colons
+           shift     # line starts with : so 1st field is empty
+           local pnn="$1" ; shift
+           shift # ignore IP address but need status bits below
+
+           case "$status" in
+               healthy)
+                   # If any bit is 1, don't match this address.
+                   local s
+                   for s ; do
+                       [ "$s" != "1" ] || continue 2
+                   done
+                   ;;
+               connected)
+                   # If disconnected bit is not 0, don't match this address.
+                   [ "$1" = "0" ] || continue
+                   ;;
+               *)
+                   invalid_nodespec
+           esac
 
-    echo $nodes
+           # Intentional multi-word expansion
+           # shellcheck disable=SC2086
+           echo_nth "$pnn" $all_nodes
+       done <<<"$ctdb_status_output"
+    )
 }
 
-ctdb_recmaster=""
-get_nodes ()
+get_any_available_node ()
 {
-    local all_nodes
-
-    if [ -n "$CTDB_NODES_SOCKETS" ] ; then 
-       all_nodes="$CTDB_NODES_SOCKETS"
-    else
-       [ -f "$CTDB_NODES_FILE" ] || CTDB_NODES_FILE=/etc/ctdb/nodes
-       all_nodes=$(egrep '^[[:alnum:]]' $CTDB_NODES_FILE)
-    fi
+    local all_nodes="$1"
 
-    local nodes=""
-    local n
-    for n in $(parse_nodespec "$1") ; do
-       [ $? != 0 ] && exit 1  # Required to catch exit in above subshell.
-       case "$n" in
-           all)
-               echo $all_nodes ;;
-           ok|healthy|con|connected) 
-               get_nodes_with_status "$all_nodes" "$n" || exit 1
-               ;;
-           rm|recmaster)
-               if [ -z "$ctdb_recmaster" ] ; then
-                   ctdb_recmaster=$(ctdb recmaster 2>/dev/null)
-                   [ $? -eq 0 ] || ctdb_recmaster=""
-               fi
-               if [ -n "$ctdb_recmaster" ] ; then
-                   echo_nth "$ctdb_recmaster" $all_nodes
-               else
-                   echo "${prog}: No recmaster available" >&2
-                   exit 1
-               fi
-               
-               ;;
-           *)
-               echo_nth $n $all_nodes
-       esac
-       
-    done
+    # We do a recursive onnode to find which nodes are up and running.
+    local out line
+    out=$("$0" -pq all ctdb pnn 2>&1)
+    while read line ; do
+       if [[ "$line" =~ ^[0-9]+$ ]] ; then
+           local pnn="$line"
+           # Intentional multi-word expansion
+           # shellcheck disable=SC2086
+           echo_nth "$pnn" $all_nodes
+           return 0
+       fi
+       # Else must be an error message from a down node.
+    done <<<"$out"
+    return 1
 }
 
-fakessh ()
+get_nodes ()
 {
-    CTDB_SOCKET="$1" sh -c "$2"
-}
+       local all_nodes
+
+       local f="${CTDB_BASE}/nodes"
+       if [ -n "$ctdb_nodes_file" ] ; then
+               f="$ctdb_nodes_file"
+               if [ ! -e "$f" ] && [ "${f#/}" = "$f" ] ; then
+                       # $f is relative, try in $CTDB_BASE
+                       f="${CTDB_BASE}/${f}"
+               fi
+       fi
 
-stdout_filter ()
-{
-    if [ -n "$prefix" ] ; then
-       cat >"${prefix}.${n}"
-    elif $verbose && $parallel ; then
-       sed -e "s@^@[$n] @"
-    else
-       cat
-    fi
+       if [ ! -r "$f" ] ; then
+               echo "${prog}: unable to open nodes file  \"${f}\"" >&2
+               exit 1
+       fi
+
+       all_nodes=$(sed -e 's@#.*@@g' -e 's@ *@@g' -e 's@^$@#DEAD@' "$f")
+
+       local n nodes
+       nodes=$(parse_nodespec "$1") || exit $?
+       for n in $nodes ; do
+               case "$n" in
+               all)
+                       echo "${all_nodes//#DEAD/}"
+                       ;;
+               any)
+                       get_any_available_node "$all_nodes" || exit 1
+                       ;;
+               ok|healthy)
+                       get_nodes_with_status "$all_nodes" "healthy" || exit 1
+                       ;;
+               con|connected)
+                       get_nodes_with_status "$all_nodes" "connected" || exit 1
+                       ;;
+               [0-9]|[0-9][0-9]|[0-9][0-9][0-9])
+                       # Intentional multi-word expansion
+                       # shellcheck disable=SC2086
+                       echo_nth "$n" $all_nodes
+                       ;;
+               *)
+                       $names_ok || invalid_nodespec
+                       echo "$n"
+               esac
+       done
 }
 
-stderr_filter ()
+push ()
 {
-    if $verbose && $parallel ; then
-       sed -e "s@^@[$n] @"
-    else
-       cat
-    fi
+       local host="$1"
+       local files="$2"
+
+       local f
+       for f in $files ; do
+               $verbose && echo "Pushing $f"
+               case "$f" in
+               /*) rsync "$f" "[${host}]:${f}" ;;
+               *)  rsync "${PWD}/${f}" "[${host}]:${PWD}/${f}" ;;
+               esac
+       done
 }
 
 ######################################################################
 
 parse_options "$@"
 
-$current && command="cd $PWD && $command"
-
 ssh_opts=
-if [ -n "$CTDB_NODES_SOCKETS" ] ; then
-    SSH=fakessh
-else 
-    # 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
+if $push ; then
+       ONNODE_SSH=push
+else
+       $current && command="cd $PWD && $command"
+
+       # Could "2>/dev/null || true" but want to see errors from typos in file.
+       [ -r "${CTDB_BASE}/onnode.conf" ] && . "${CTDB_BASE}/onnode.conf"
+       [ -n "$ONNODE_SSH" ] || ONNODE_SSH=ssh
+       # $ONNODE_SSH must accept the -n option - it can be ignored!
+       if $parallel || ! $stdin ; then
+               ssh_opts="-n"
+       fi
 fi
 
 ######################################################################
 
-nodes=$(get_nodes "$nodespec")
-[ $? != 0 ] && exit 1   # Required to catch exit in above subshell.
+nodes=$(get_nodes "$nodespec") || exit $?
 
 if $quiet ; then
     verbose=false
@@ -280,31 +302,38 @@ else
 fi
 
 pids=""
+# Intentional multi-word expansion
+# shellcheck disable=SC2086
 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.
 retcode=0
 for n in $nodes ; do
-    set -o pipefail 2>/dev/null
-    if $parallel ; then
-       { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; } &
-       pids="${pids} $!"
-    else
-       if $verbose ; then
-           echo >&2 ; echo ">> NODE: $n <<" >&2
-       fi
+       set -o pipefail 2>/dev/null
 
-       { exec 3>&1 ; { $SSH $ssh_opts $EXTRA_SSH_OPTS $n "$command" | stdout_filter >&3 ; } 2>&1 | stderr_filter ; }
-       [ $? = 0 ] || retcode=$?
-    fi
+       ssh_cmd="$ONNODE_SSH $ssh_opts"
+       if $parallel ; then
+               if $verbose ; then
+                       $ssh_cmd "$n" "$command" 2>&1 | sed -e "s@^@[$n] @"
+               else
+                       $ssh_cmd "$n" "$command"
+               fi &
+               pids="${pids} $!"
+       else
+               if $verbose ; then
+                       echo >&2 ; echo ">> NODE: $n <<" >&2
+               fi
+               {
+                       $ssh_cmd "$n" "$command"
+               } || retcode=$?
+       fi
 done
 
-$parallel && {
-    for p in $pids; do
-       wait $p
-       [ $? = 0 ] || retcode=$?
-    done
-}
+if $parallel ; then
+       for p in $pids; do
+               wait "$p" || retcode=$?
+       done
+fi
 
 exit $retcode