e2a9b03c8434e42db2233bba6e3a21b8125cddae
[vlendec/samba-autobuild/.git] / ctdb / config / functions
1 # Hey Emacs, this is a -*- shell-script -*- !!!
2
3 # utility functions for ctdb event scripts
4
5 PATH=/bin:/usr/bin:/usr/sbin:/sbin:$PATH
6
7 [ -z "$CTDB_VARDIR" ] && {
8     if [ -d "/var/lib/ctdb" ] ; then
9         export CTDB_VARDIR="/var/lib/ctdb"
10     else
11         export CTDB_VARDIR="/var/ctdb"
12     fi
13 }
14 [ -z "$CTDB_ETCDIR" ] && {
15     export CTDB_ETCDIR="/etc"
16 }
17
18 #######################################
19 # pull in a system config file, if any
20 _loadconfig() {
21
22     if [ -z "$1" ] ; then
23         foo="${service_config:-${service_name}}"
24         if [ -n "$foo" ] ; then
25             loadconfig "$foo"
26         fi
27     elif [ "$1" != "ctdb" ] ; then
28         loadconfig "ctdb"
29     fi
30
31     if [ -f $CTDB_ETCDIR/sysconfig/$1 ]; then
32         . $CTDB_ETCDIR/sysconfig/$1
33     elif [ -f $CTDB_ETCDIR/default/$1 ]; then
34         . $CTDB_ETCDIR/default/$1
35     elif [ -f $CTDB_BASE/sysconfig/$1 ]; then
36         . $CTDB_BASE/sysconfig/$1
37     fi
38 }
39
40 loadconfig () {
41     _loadconfig "$@"
42 }
43
44 ##############################################################
45 # make sure CTDB_CURRENT_DEBUGLEVEL is set to the desired debug level
46 # (integer)
47 #
48 # If it is already set then do nothing, since it might have been set
49 # via a file in rc.local.d/.  If it is not set then set it by sourcing
50 # /var/ctdb/eventscript_debuglevel. If this file does not exist then
51 # create it using output from "ctdb getdebug".  If the option 1st arg
52 # is "create" then don't source an existing file but create a new one
53 # instead - this is useful for creating the file just once in each
54 # event run in 00.ctdb.  If there's a problem getting the debug level
55 # from ctdb then it is silently set to 0 - no use spamming logs if our
56 # debug code is broken...
57 ctdb_set_current_debuglevel ()
58 {
59     [ -z "$CTDB_CURRENT_DEBUGLEVEL" ] || return 0
60
61     _f="$CTDB_VARDIR/eventscript_debuglevel"
62
63     if [ "$1" = "create" -o ! -r "$_f" ] ; then
64         _t=$(ctdb getdebug -Y 2>/dev/null)
65         # get last field of output
66         _t="${_t%:}"
67         _t="${_t##*:}"
68         # Defaults to 0
69         echo "export CTDB_CURRENT_DEBUGLEVEL=\"${_t:-0}\"" >"$_f"
70     fi
71
72     . "$_f"
73 }
74
75 debug ()
76 {
77     if [ $CTDB_CURRENT_DEBUGLEVEL -ge 4 ] ; then
78         # If there are arguments then echo them.  Otherwise expect to
79         # use stdin, which allows us to pass lots of debug using a
80         # here document.
81         if [ -n "$1" ] ; then
82             echo "DEBUG: $*"
83         elif ! tty -s ; then
84             sed -e 's@^@DEBUG: @'
85         fi
86     fi
87 }
88
89 die ()
90 {
91     _msg="$1"
92     _rc="${2:-1}"
93
94     echo "$_msg"
95     exit $_rc
96 }
97
98 # When things are run in the background in an eventscript then logging
99 # output might get lost.  This is the "solution".  :-)
100 background_with_logging ()
101 {
102     _using_syslog=false
103     if [ "$CTDB_SYSLOG" = "yes" -o -z "$CTDB_LOGFILE" ] ; then
104         _using_syslog=true
105     fi
106     case "$CTDB_OPTIONS" in
107         *--syslog*) _using_syslog=true ;;
108     esac
109
110     (
111         "$@" 2>&1 </dev/null |
112         if $_using_syslog ; then
113             logger -t "ctdbd: ${script_name}&"
114         else
115             cat >>"$CTDB_LOGFILE"
116         fi
117     )&
118
119     return 0
120 }
121
122 ##############################################################
123 # check number of args for different events
124 ctdb_check_args ()
125 {
126     case "$1" in
127         takeip|releaseip)
128             if [ $# != 4 ]; then
129                 echo "ERROR: must supply interface, IP and maskbits"
130                 exit 1
131             fi
132             ;;
133         updateip)
134             if [ $# != 5 ]; then
135                 echo "ERROR: must supply old interface, new interface, IP and maskbits"
136                 exit 1
137             fi
138             ;;
139     esac
140 }
141
142 ##############################################################
143 # determine on what type of system (init style) we are running
144 detect_init_style() {
145     # only do detection if not already set:
146     test "x$CTDB_INIT_STYLE" != "x" && return
147
148     if [ -x /sbin/startproc ]; then
149         CTDB_INIT_STYLE="suse"
150     elif [ -x /sbin/start-stop-daemon ]; then
151         CTDB_INIT_STYLE="debian"
152     else
153         CTDB_INIT_STYLE="redhat"
154     fi
155 }
156
157 ######################################################
158 # simulate /sbin/service on platforms that don't have it
159 # _service() makes it easier to hook the service() function for
160 # testing.
161 _service ()
162 {
163   _service_name="$1"
164   _op="$2"
165
166   # do nothing, when no service was specified
167   [ -z "$_service_name" ] && return
168
169   if [ -x /sbin/service ]; then
170       $_nice /sbin/service "$_service_name" "$_op"
171   elif [ -x $CTDB_ETCDIR/init.d/$_service_name ]; then
172       $_nice $CTDB_ETCDIR/init.d/$_service_name "$_op"
173   elif [ -x $CTDB_ETCDIR/rc.d/init.d/$_service_name ]; then
174       $_nice $CTDB_ETCDIR/rc.d/init.d/$_service_name "$_op"
175   fi
176 }
177
178 service()
179 {
180     _nice=""
181     _service "$@"
182 }
183
184 ######################################################
185 # simulate /sbin/service (niced) on platforms that don't have it
186 nice_service()
187 {
188     _nice="nice"
189     _service "$@"
190 }
191
192 ######################################################
193 # wrapper around /proc/ settings to allow them to be hooked
194 # for testing
195 # 1st arg is relative path under /proc/, 2nd arg is value to set
196 set_proc ()
197 {
198     echo "$2" >"/proc/$1"
199 }
200
201 ######################################################
202 # wrapper around getting file contents from /proc/ to allow
203 # this to be hooked for testing
204 # 1st arg is relative path under /proc/
205 get_proc ()
206 {
207     cat "/proc/$1"
208 }
209
210 ######################################################
211 # Check that an RPC service is healthy -
212 # this includes allowing a certain number of failures
213 # before marking the NFS service unhealthy.
214 #
215 # usage: nfs_check_rpc_service SERVICE_NAME [ triple ...]
216 #
217 # each triple is a set of 3 arguments: an operator, a 
218 # fail count limit and an action string.
219 #
220 # For example:
221 #
222 #       nfs_check_rpc_service "lockd" \
223 #           -ge 15 "verbose restart unhealthy" \
224 #           -eq 10 "restart:bs"
225 #
226 # says that if lockd is down for 15 iterations then do
227 # a verbose restart of lockd and mark the node unhealthy.
228 # Before this, after 10 iterations of failure, the
229 # service is restarted silently in the background.
230 # Order is important: the number of failures need to be
231 # specified in reverse order because processing stops
232 # after the first condition that is true.
233 ######################################################
234 nfs_check_rpc_service ()
235 {
236     _prog_name="$1" ; shift
237
238     _v=""
239     case "$1" in
240         -*) : ;;
241         *) _v="$1" ; shift ;;
242     esac
243
244     _version=${_v:-1}
245     _rpc_prog="$_prog_name"
246     _restart=""
247     _opts=""
248     case "$_prog_name" in
249         knfsd)
250             _rpc_prog=nfs
251             _version=${_v:-3}
252             _restart="echo 'Trying to restart NFS service'"
253             _restart="${_restart}; startstop_nfs restart"
254             ;;
255         ganesha)
256             _rpc_prog=nfs
257             _version=${_v:-3}
258             _restart="echo 'Trying to restart Ganesha NFS service'"
259             _restart="${_restart}; startstop_ganesha restart"
260             ;;
261         mountd)
262             _opts="${MOUNTD_PORT:+ -p }${MOUNTD_PORT}"
263             ;;
264         rquotad)
265             _opts="${RQUOTAD_PORT:+ -p }${RQUOTAD_PORT}"
266             ;;
267         lockd)
268             _rpc_prog=nlockmgr
269             _version=${_v:-4}
270             _restart="echo 'Trying to restart lock manager service'"
271             _restart="${_restart}; startstop_nfslock restart"
272             ;;
273         statd)
274             _rpc_prog=status
275             _opts="${STATD_HOSTNAME:+ -n }${STATD_HOSTNAME}"
276             _opts="${_opts}${STATD_PORT:+ -p }${STATD_PORT}"
277             _opts="${_opts}${STATD_OUTGOING_PORT:+ -o }${STATD_OUTGOING_PORT}"
278             ;;
279         *)
280             echo "Internal error: unknown RPC program \"$_prog_name\"."
281             exit 1
282     esac
283
284     _service_name="nfs_${_prog_name}"
285
286     if ctdb_check_rpc "$_rpc_prog" $_version >/dev/null ; then
287         ctdb_counter_init "$_service_name"
288         return 0
289     fi
290
291     ctdb_counter_incr "$_service_name"
292
293     while [ -n "$3" ] ; do
294         ctdb_check_counter "quiet" "$1" "$2" "$_service_name" || {
295             for _action in $3 ; do
296                 case "$_action" in
297                     verbose)
298                         echo "$ctdb_check_rpc_out"
299                         ;;
300                     restart|restart:*)
301                         # No explicit command specified, construct rpc command.
302                         if [ -z "$_restart" ] ; then
303                             _p="rpc.${_prog_name}"
304                             _restart="echo 'Trying to restart $_prog_name [${_p}${_opts}]'"
305                             _restart="${_restart}; killall -q -9 $_p"
306                             _restart="${_restart}; $_p $_opts"
307                         fi
308
309                         # Process restart flags...
310                         _flags="${_action#restart:}"
311                         # There may not have been a colon...
312                         [ "$_flags" != "$_action" ] || _flags=""
313                         # q=quiet - everything to /dev/null
314                         if [ "${_flags#*q}" != "$_flags" ] ; then
315                             _restart="{ ${_restart} ; } >/dev/null 2>&1"
316                         fi
317                         # s=stealthy - last command to /dev/null
318                         if [ "${_flags#*s}" != "$_flags" ] ; then
319                             _restart="${_restart} >/dev/null 2>&1"
320                         fi
321                         # b=background - the whole thing, easy and reliable
322                         if [ "${_flags#*b}" != "$_flags" ] ; then
323                             _restart="{ ${_restart} ; } &"
324                         fi
325
326                         # Do it!
327                         eval "${_restart}"
328                         ;;
329                     unhealthy)
330                         exit 1
331                         ;;
332                     *)
333                         echo "Internal error: unknown action \"$_action\"."
334                         exit 1
335                 esac
336             done
337
338             # Only process the first action group.
339             break
340         }
341         shift 3
342     done
343 }
344
345 ######################################################
346 # check that a rpc server is registered with portmap
347 # and responding to requests
348 # usage: ctdb_check_rpc SERVICE_NAME VERSION
349 ######################################################
350 ctdb_check_rpc ()
351 {
352     progname="$1"
353     version="$2"
354
355     if ! ctdb_check_rpc_out=$(rpcinfo -u localhost $progname $version 2>&1) ; then
356         ctdb_check_rpc_out="ERROR: $progname failed RPC check:
357 $ctdb_check_rpc_out"
358         echo "$ctdb_check_rpc_out"
359         return 1
360     fi
361 }
362
363 ######################################################
364 # check a set of directories is available
365 # return 1 on a missing directory
366 # usage: ctdb_check_directories_probe SERVICE_NAME <directories...>
367 ######################################################
368 ctdb_check_directories_probe() {
369     while IFS="" read d ; do
370         case "$d" in
371             *%*)
372                 continue
373                 ;;
374             *)
375                 [ -d "${d}/." ] || return 1
376         esac
377     done
378 }
379
380 ######################################################
381 # check a set of directories is available
382 # usage: ctdb_check_directories SERVICE_NAME <directories...>
383 ######################################################
384 ctdb_check_directories() {
385     n="${1:-${service_name}}"
386     ctdb_check_directories_probe || {
387         echo "ERROR: $n directory \"$d\" not available"
388         exit 1
389     }
390 }
391
392 ######################################################
393 # check a set of tcp ports
394 # usage: ctdb_check_tcp_ports <ports...>
395 ######################################################
396
397 # This flag file is created when a service is initially started.  It
398 # is deleted the first time TCP port checks for that service succeed.
399 # Until then ctdb_check_tcp_ports() prints a more subtle "error"
400 # message if a port check fails.
401 _ctdb_check_tcp_common ()
402 {
403     _ctdb_service_started_file="$ctdb_fail_dir/$service_name.started"
404 }
405
406 ctdb_check_tcp_init ()
407 {
408     _ctdb_check_tcp_common
409     mkdir -p "${_ctdb_service_started_file%/*}" # dirname
410     touch "$_ctdb_service_started_file"
411 }
412
413 ctdb_check_tcp_ports()
414 {
415     if [ -z "$1" ] ; then
416         echo "INTERNAL ERROR: ctdb_check_tcp_ports - no ports specified"
417         exit 1
418     fi
419
420     # Set default value for CTDB_TCP_PORT_CHECKS if unset.
421     # If any of these defaults are unsupported then this variable can
422     # be overridden in /etc/sysconfig/ctdb or via a file in
423     # /etc/ctdb/rc.local.d/.
424     : ${CTDB_TCP_PORT_CHECKERS:=ctdb nmap netstat}
425
426     for _c in $CTDB_TCP_PORT_CHECKERS ; do
427         ctdb_check_tcp_ports_$_c "$@"
428         case "$?" in
429             0)
430                 _ctdb_check_tcp_common
431                 rm -f "$_ctdb_service_started_file"
432                 return 0
433                 ;;
434             1)
435                 _ctdb_check_tcp_common
436                 if [ ! -f "$_ctdb_service_started_file" ] ; then
437                     echo "ERROR: $service_name tcp port $_p is not responding"
438                     debug <<EOF
439 $ctdb_check_tcp_ports_debug
440 EOF
441                 else
442                     echo "INFO: $service_name tcp port $_p is not responding"
443                 fi
444
445                 return 1
446                 ;;
447             127)
448                 debug <<EOF
449 ctdb_check_ports - checker $_c not implemented
450 output from checker was:
451 $ctdb_check_tcp_ports_debug
452 EOF
453                 ;;
454             *)
455                 
456         esac
457     done
458
459     echo "INTERNAL ERROR: ctdb_check_ports - no working checkers in CTDB_TCP_PORT_CHECKERS=\"$CTDB_TCP_PORT_CHECKERS\""
460
461     return 127
462 }
463
464 ctdb_check_tcp_ports_netstat ()
465 {
466     _cmd='netstat -l -t -n'
467     _ns=$($_cmd 2>&1)
468     if [ $? -eq 127 ] ; then
469         # netstat probably not installed - unlikely?
470         ctdb_check_tcp_ports_debug="$_ns"
471         return 127
472     fi
473
474     for _p ; do  # process each function argument (port)
475         for _a in '0\.0\.0\.0' '::' ; do
476             _pat="[[:space:]]${_a}:${_p}[[:space:]]+[^[:space:]]+[[:space:]]+LISTEN"
477             if echo "$_ns" | grep -E -q "$_pat" ; then
478                 # We matched the port, so process next port
479                 continue 2
480             fi
481         done
482
483         # We didn't match the port, so flag an error.
484         ctdb_check_tcp_ports_debug="$_cmd shows this output:
485 $_ns"
486         return 1
487     done
488
489     return 0
490 }
491
492 ctdb_check_tcp_ports_nmap ()
493 {
494     # nmap wants a comma-separated list of ports
495     _ports=""
496     for _p ; do
497         _ports="${_ports}${_ports:+,}${_p}"
498     done
499
500     _cmd="nmap -n -oG - -PS 127.0.0.1 -p $_ports"
501
502     _nmap_out=$($_cmd 2>&1)
503     if [ $? -eq 127 ] ; then
504         # nmap probably not installed
505         ctdb_check_tcp_ports_debug="$_nmap_out"
506         return 127
507     fi
508
509     # get the port-related output
510     _port_info=$(echo "$_nmap_out" | sed -n -r -e 's@^.*Ports:[[:space:]]@@p')
511
512     for _p ; do
513         # looking for something like this:
514         #  445/open/tcp//microsoft-ds///
515         # possibly followed by a comma
516         _t="$_p/open/tcp//"
517         case "$_port_info" in
518             # The info we're after must be either at the beginning of
519             # the string or it must follow a space.
520             $_t*|*\ $_t*) : ;;
521             *)
522                 # Nope, flag an error...
523                 ctdb_check_tcp_ports_debug="$_cmd shows this output:
524 $_nmap_out"
525                 return 1
526         esac
527     done
528
529     return 0
530 }
531
532 # Use the new "ctdb checktcpport" command to check the port.
533 # This is very cheap.
534 ctdb_check_tcp_ports_ctdb ()
535 {
536     for _p ; do  # process each function argument (port)
537         _cmd="ctdb checktcpport $_p"
538         _out=$($_cmd 2>&1)
539         _ret=$?
540         case "$_ret" in
541             0)
542                 ctdb_check_tcp_ports_debug="\"$_cmd\" was able to bind to port"
543                 return 1
544                 ;;
545             98)
546                 # Couldn't bind, something already listening, next port...
547                 continue
548                 ;;
549             *)
550                 ctdb_check_tcp_ports_debug="$_cmd (exited with $_ret) with output:
551 $_out"
552                 # assume not implemented
553                 return 127
554         esac
555     done
556
557     return 0
558 }
559
560 ######################################################
561 # check a unix socket
562 # usage: ctdb_check_unix_socket SERVICE_NAME <socket_path>
563 ######################################################
564 ctdb_check_unix_socket() {
565     socket_path="$1"
566     [ -z "$socket_path" ] && return
567
568     if ! netstat --unix -a -n | grep -q "^unix.*LISTEN.*${socket_path}$"; then
569         echo "ERROR: $service_name socket $socket_path not found"
570         return 1
571     fi
572 }
573
574 ######################################################
575 # check a command returns zero status
576 # usage: ctdb_check_command SERVICE_NAME <command>
577 ######################################################
578 ctdb_check_command() {
579   service_name="$1"
580   wait_cmd="$2"
581   [ -z "$wait_cmd" ] && return;
582   $wait_cmd > /dev/null 2>&1 || {
583       echo "ERROR: $service_name - $wait_cmd returned error"
584       exit 1
585   }
586 }
587
588 ################################################
589 # kill off any TCP connections with the given IP
590 ################################################
591 kill_tcp_connections() {
592     _IP="$1"    
593     _failed=0
594
595     _killcount=0
596     connfile="$CTDB_VARDIR/state/connections.$_IP"
597     netstat -tn |egrep "^tcp.*[[:space:]]+$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' > $connfile
598     netstat -tn |egrep "^tcp.*[[:space:]]+::ffff:$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' >> $connfile
599
600     while read dest src; do
601         srcip=`echo $src | sed -e "s/:[^:]*$//"`
602         srcport=`echo $src | sed -e "s/^.*://"`
603         destip=`echo $dest | sed -e "s/:[^:]*$//"`
604         destport=`echo $dest | sed -e "s/^.*://"`
605         echo "Killing TCP connection $srcip:$srcport $destip:$destport"
606         ctdb killtcp $srcip:$srcport $destip:$destport >/dev/null 2>&1 || _failed=1
607         case $destport in
608           # we only do one-way killtcp for CIFS
609           139|445) : ;;
610           # for all others we do 2-way
611           *) 
612                 ctdb killtcp $destip:$destport $srcip:$srcport >/dev/null 2>&1 || _failed=1
613                 ;;
614         esac
615         _killcount=`expr $_killcount + 1`
616      done < $connfile
617     rm -f $connfile
618
619     [ $_failed = 0 ] || {
620         echo "Failed to send killtcp control"
621         return;
622     }
623     [ $_killcount -gt 0 ] || {
624         return;
625     }
626     _count=0
627     while netstat -tn |egrep "^tcp.*[[:space:]]+$_IP:.*ESTABLISHED" > /dev/null; do
628         sleep 1
629         _count=`expr $_count + 1`
630         [ $_count -gt 3 ] && {
631             echo "Timed out killing tcp connections for IP $_IP"
632             return;
633         }
634     done
635     echo "killed $_killcount TCP connections to released IP $_IP"
636 }
637
638 ##################################################################
639 # kill off the local end for any TCP connections with the given IP
640 ##################################################################
641 kill_tcp_connections_local_only() {
642     _IP="$1"    
643     _failed=0
644
645     _killcount=0
646     connfile="$CTDB_VARDIR/state/connections.$_IP"
647     netstat -tn |egrep "^tcp.*[[:space:]]+$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' > $connfile
648     netstat -tn |egrep "^tcp.*[[:space:]]+::ffff:$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' >> $connfile
649
650     while read dest src; do
651         srcip=`echo $src | sed -e "s/:[^:]*$//"`
652         srcport=`echo $src | sed -e "s/^.*://"`
653         destip=`echo $dest | sed -e "s/:[^:]*$//"`
654         destport=`echo $dest | sed -e "s/^.*://"`
655         echo "Killing TCP connection $srcip:$srcport $destip:$destport"
656         ctdb killtcp $srcip:$srcport $destip:$destport >/dev/null 2>&1 || _failed=1
657         _killcount=`expr $_killcount + 1`
658      done < $connfile
659     rm -f $connfile
660
661     [ $_failed = 0 ] || {
662         echo "Failed to send killtcp control"
663         return;
664     }
665     [ $_killcount -gt 0 ] || {
666         return;
667     }
668     _count=0
669     while netstat -tn |egrep "^tcp.*[[:space:]]+$_IP:.*ESTABLISHED" > /dev/null; do
670         sleep 1
671         _count=`expr $_count + 1`
672         [ $_count -gt 3 ] && {
673             echo "Timed out killing tcp connections for IP $_IP"
674             return;
675         }
676     done
677     echo "killed $_killcount TCP connections to released IP $_IP"
678 }
679
680 ##################################################################
681 # tickle any TCP connections with the given IP
682 ##################################################################
683 tickle_tcp_connections() {
684     _IP="$1"
685     _failed=0
686
687     _killcount=0
688     connfile="$CTDB_VARDIR/state/connections.$_IP"
689     netstat -tn |egrep "^tcp.*[[:space:]]+$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' > $connfile
690     netstat -tn |egrep "^tcp.*[[:space:]]+::ffff:$_IP:.*ESTABLISHED" | awk '{print $4" "$5}' >> $connfile
691
692     while read dest src; do
693         srcip=`echo $src | sed -e "s/:[^:]*$//"`
694         srcport=`echo $src | sed -e "s/^.*://"`
695         destip=`echo $dest | sed -e "s/:[^:]*$//"`
696         destport=`echo $dest | sed -e "s/^.*://"`
697         echo "Tickle TCP connection $srcip:$srcport $destip:$destport"
698         ctdb tickle $srcip:$srcport $destip:$destport >/dev/null 2>&1 || _failed=1
699         echo "Tickle TCP connection $destip:$destport $srcip:$srcport"
700         ctdb tickle $destip:$destport $srcip:$srcport >/dev/null 2>&1 || _failed=1
701      done < $connfile
702     rm -f $connfile
703
704     [ $_failed = 0 ] || {
705         echo "Failed to send tickle control"
706         return;
707     }
708 }
709
710 ########################################################
711 # start/stop the Ganesha nfs service
712 ########################################################
713 startstop_ganesha()
714 {
715     _ganesha_fsal_list="gpfs"
716     for _fsal in $_ganesha_fsal_list ; do
717         _service_name="nfs-ganesha-${_fsal}"
718         if [ -x /etc/init.d/$_service_name ] ; then
719             break
720         fi
721     done
722     case "$1" in
723         start)
724             service "$_service_name" start
725             ;;
726         stop)
727             service "$_service_name" stop
728             ;;
729         restart)
730             service "$_service_name" restart
731             ;;
732     esac
733 }
734
735 ########################################################
736 # start/stop the nfs service on different platforms
737 ########################################################
738 startstop_nfs() {
739         PLATFORM="unknown"
740         [ -x $CTDB_ETCDIR/init.d/nfsserver ] && {
741                 PLATFORM="sles"
742         }
743         [ -x $CTDB_ETCDIR/init.d/nfslock ] && {
744                 PLATFORM="rhel"
745         }
746
747         case $PLATFORM in
748         sles)
749                 case $1 in
750                 start)
751                         service nfsserver start
752                         ;;
753                 stop)
754                         service nfsserver stop > /dev/null 2>&1
755                         ;;
756                 restart)
757                         set_proc "fs/nfsd/threads" 0
758                         service nfsserver stop > /dev/null 2>&1
759                         pkill -9 nfsd
760                         service nfsserver start
761                         ;;
762                 esac
763                 ;;
764         rhel)
765                 case $1 in
766                 start)
767                         service nfslock start
768                         service nfs start
769                         ;;
770                 stop)
771                         service nfs stop
772                         service nfslock stop
773                         ;;
774                 restart)
775                         set_proc "fs/nfsd/threads" 0
776                         service nfs stop > /dev/null 2>&1
777                         service nfslock stop > /dev/null 2>&1
778                         pkill -9 nfsd
779                         service nfslock start
780                         service nfs start
781                         ;;
782                 esac
783                 ;;
784         *)
785                 echo "Unknown platform. NFS is not supported with ctdb"
786                 exit 1
787                 ;;
788         esac
789 }
790
791 ########################################################
792 # start/stop the nfs lockmanager service on different platforms
793 ########################################################
794 startstop_nfslock() {
795         PLATFORM="unknown"
796         [ -x $CTDB_ETCDIR/init.d/nfsserver ] && {
797                 PLATFORM="sles"
798         }
799         [ -x $CTDB_ETCDIR/init.d/nfslock ] && {
800                 PLATFORM="rhel"
801         }
802
803         case $PLATFORM in
804         sles)
805                 # for sles there is no service for lockmanager
806                 # so we instead just shutdown/restart nfs
807                 case $1 in
808                 start)
809                         service nfsserver start
810                         ;;
811                 stop)
812                         service nfsserver stop > /dev/null 2>&1
813                         ;;
814                 restart)
815                         service nfsserver stop
816                         service nfsserver start
817                         ;;
818                 esac
819                 ;;
820         rhel)
821                 case $1 in
822                 start)
823                         service nfslock start
824                         ;;
825                 stop)
826                         service nfslock stop > /dev/null 2>&1
827                         ;;
828                 restart)
829                         service nfslock stop
830                         service nfslock start
831                         ;;
832                 esac
833                 ;;
834         *)
835                 echo "Unknown platform. NFS locking is not supported with ctdb"
836                 exit 1
837                 ;;
838         esac
839 }
840
841 add_ip_to_iface()
842 {
843     _iface=$1
844     _ip=$2
845     _maskbits=$3
846
847     _lockfile="${CTDB_VARDIR}/state/interface_modify_${_iface}.flock"
848     [ -f "$_lockfile" ] || touch "$_lockfile"
849
850     (
851         # Note: use of return/exit/die() below only gets us out of the
852         # sub-shell, which is actually what we want.  That is, the
853         # function should just return non-zero.
854
855         flock --timeout 30 0 || \
856             die "add_ip_to_iface: unable to get lock for ${_iface}"
857
858         # Ensure interface is up
859         ip link set "$_iface" up || \
860             die "Failed to bringup interface $_iface"
861
862         ip addr add "$_ip/$_maskbits" brd + dev "$_iface" || \
863             die "Failed to add $_ip/$_maskbits on dev $_iface"
864     ) <"$_lockfile"
865
866     # Do nothing here - return above only gets us out of the subshell
867     # and doing anything here will affect the return code.
868 }
869
870 delete_ip_from_iface()
871 {
872     _iface=$1
873     _ip=$2
874     _maskbits=$3
875
876     _lockfile="${CTDB_VARDIR}/state/interface_modify_${_iface}.flock"
877     [ -f "$_lockfile" ] || touch "$_lockfile"
878
879     (
880         # Note: use of return/exit/die() below only gets us out of the
881         # sub-shell, which is actually what we want.  That is, the
882         # function should just return non-zero.
883
884         flock --timeout 30 0 || \
885             die "delete_ip_from_iface: unable to get lock for ${_iface}"
886
887         _im="$_ip/$_maskbits"  # shorthand for readability
888
889         # "ip addr del" will delete all secondary IPs if this is the
890         # primary.  To work around this _very_ annoying behaviour we
891         # have to keep a record of the secondaries and re-add them
892         # afterwards.  Yuck!
893
894         _secondaries=""
895         if ip addr list dev "$_iface" primary | grep -Fq "inet $_im " ; then
896             _secondaries=$(ip addr list dev "$_iface" secondary | \
897                 awk '$1 == "inet" { print $2 }')
898         fi
899
900         local _rc=0
901         ip addr del "$_im" dev "$_iface" || {
902             echo "Failed to del $_ip on dev $_iface"
903             _rc=1
904         }
905
906         if [ -n "$_secondaries" ] ; then
907             for _i in $_secondaries; do
908                 if ip addr list dev "$_iface" | grep -Fq "inet $_i" ; then
909                     echo "Kept secondary $_i on dev $_iface"
910                 else
911                     echo "Re-adding secondary address $_i to dev $_iface"
912                     ip addr add $_i brd + dev $_iface || {
913                         echo "Failed to re-add address $_i to dev $_iface"
914                         _rc=1
915                     }
916                 fi
917             done
918         fi
919
920         return $_rc
921     ) <"$_lockfile"
922
923     # Do nothing here - return above only gets us out of the subshell
924     # and doing anything here will affect the return code.
925 }
926
927 ########################################################
928 # some simple logic for counting events - per eventscript
929 # usage: ctdb_counter_init
930 #        ctdb_counter_incr
931 #        ctdb_check_counter_limit <limit>
932 # ctdb_check_counter_limit succeeds when count >= <limit>
933 ########################################################
934 _ctdb_counter_common () {
935     _service_name="${1:-${service_name}}"
936     _counter_file="$ctdb_fail_dir/$_service_name"
937     mkdir -p "${_counter_file%/*}" # dirname
938 }
939 ctdb_counter_init () {
940     _ctdb_counter_common "$1"
941
942     >"$_counter_file"
943 }
944 ctdb_counter_incr () {
945     _ctdb_counter_common "$1"
946
947     # unary counting!
948     echo -n 1 >> "$_counter_file"
949 }
950 ctdb_check_counter_limit () {
951     _ctdb_counter_common
952
953     _limit="${1:-${service_fail_limit}}"
954     _quiet="$2"
955
956     # unary counting!
957     _size=$(stat -c "%s" "$_counter_file" 2>/dev/null || echo 0)
958     if [ $_size -ge $_limit ] ; then
959         echo "ERROR: more than $_limit consecutive failures for $service_name, marking cluster unhealthy"
960         exit 1
961     elif [ $_size -gt 0 -a -z "$_quiet" ] ; then
962         echo "WARNING: less than $_limit consecutive failures ($_size) for $service_name, not unhealthy yet"
963     fi
964 }
965 ctdb_check_counter_equal () {
966     _ctdb_counter_common
967
968     _limit=$1
969
970     # unary counting!
971     _size=$(stat -c "%s" "$_counter_file" 2>/dev/null || echo 0)
972     if [ $_size -eq $_limit ] ; then
973         return 1
974     fi
975     return 0
976 }
977 ctdb_check_counter () {
978     _msg="${1:-error}"  # "error"  - anything else is silent on fail
979     _op="${2:--ge}"  # an integer operator supported by test
980     _limit="${3:-${service_fail_limit}}"
981     shift 3
982     _ctdb_counter_common "$1"
983
984     # unary counting!
985     _size=$(stat -c "%s" "$_counter_file" 2>/dev/null || echo 0)
986     if [ $_size $_op $_limit ] ; then
987         if [ "$_msg" = "error" ] ; then
988             echo "ERROR: $_limit consecutive failures for $_service_name, marking node unhealthy"
989             exit 1              
990         else
991             return 1
992         fi
993     fi
994 }
995
996 ########################################################
997
998 ctdb_status_dir="$CTDB_VARDIR/status"
999 ctdb_fail_dir="$CTDB_VARDIR/failcount"
1000
1001 ctdb_setup_service_state_dir ()
1002 {
1003     service_state_dir="$CTDB_VARDIR/state/${1:-${service_name}}"
1004     mkdir -p "$service_state_dir" || {
1005         echo "Error creating state dir \"$service_state_dir\""
1006         exit 1
1007     }
1008 }
1009
1010 ########################################################
1011 # Managed status history, for auto-start/stop
1012
1013 ctdb_managed_dir="$CTDB_VARDIR/managed_history"
1014
1015 _ctdb_managed_common ()
1016 {
1017     _service_name="${1:-${service_name}}"
1018     _ctdb_managed_file="$ctdb_managed_dir/$_service_name"
1019 }
1020
1021 ctdb_service_managed ()
1022 {
1023     _ctdb_managed_common "$@"
1024     mkdir -p "$ctdb_managed_dir"
1025     touch "$_ctdb_managed_file"
1026 }
1027
1028 ctdb_service_unmanaged ()
1029 {
1030     _ctdb_managed_common "$@"
1031     rm -f "$_ctdb_managed_file"
1032 }
1033
1034 is_ctdb_previously_managed_service ()
1035 {
1036     _ctdb_managed_common "$@"
1037     [ -f "$_ctdb_managed_file" ]
1038 }
1039
1040 ########################################################
1041 # Check and set status
1042
1043 log_status_cat ()
1044 {
1045     echo "node is \"$1\", \"${script_name}\" reports problem: $(cat $2)"
1046 }
1047
1048 ctdb_checkstatus ()
1049 {
1050     if [ -r "$ctdb_status_dir/$script_name/unhealthy" ] ; then
1051         log_status_cat "unhealthy" "$ctdb_status_dir/$script_name/unhealthy"
1052         return 1
1053     elif [ -r "$ctdb_status_dir/$script_name/banned" ] ; then
1054         log_status_cat "banned" "$ctdb_status_dir/$script_name/banned"
1055         return 2
1056     else
1057         return 0
1058     fi
1059 }
1060
1061 ctdb_setstatus ()
1062 {
1063     d="$ctdb_status_dir/$script_name"
1064     case "$1" in
1065         unhealthy|banned)
1066             mkdir -p "$d"
1067             cat "$2" >"$d/$1"
1068             ;;
1069         *)
1070             for i in "banned" "unhealthy" ; do
1071                 rm -f "$d/$i"
1072             done
1073             ;;
1074     esac
1075 }
1076
1077 ##################################################################
1078 # Reconfigure a service on demand
1079
1080 _ctdb_service_reconfigure_common ()
1081 {
1082     _d="$ctdb_status_dir/${1:-${service_name}}"
1083     mkdir -p "$_d"
1084     _ctdb_service_reconfigure_flag="$_d/reconfigure"
1085 }
1086
1087 ctdb_service_needs_reconfigure ()
1088 {
1089     _ctdb_service_reconfigure_common "$@"
1090     [ -e "$_ctdb_service_reconfigure_flag" ]
1091 }
1092
1093 ctdb_service_set_reconfigure ()
1094 {
1095     _ctdb_service_reconfigure_common "$@"
1096     >"$_ctdb_service_reconfigure_flag"
1097 }
1098
1099 ctdb_service_unset_reconfigure ()
1100 {
1101     _ctdb_service_reconfigure_common "$@"
1102     rm -f "$_ctdb_service_reconfigure_flag"
1103 }
1104
1105 ctdb_service_reconfigure ()
1106 {
1107     echo "Reconfiguring service \"$@\"..."
1108     ctdb_service_unset_reconfigure "$@"
1109     service_reconfigure "$@" || return $?
1110     ctdb_counter_init "$@"
1111 }
1112
1113 # Default service_reconfigure() function.
1114 service_reconfigure ()
1115 {
1116     service "${1:-$service_name}" restart
1117 }
1118
1119 ctdb_reconfigure_try_lock ()
1120 {
1121     
1122     _ctdb_service_reconfigure_common "$@"
1123     _lock="${_d}/reconfigure_lock"
1124     touch "$_lock"
1125
1126     (
1127         flock 0
1128         # This is overkill but will work if we need to extend this to
1129         # allow certain events to run multiple times in parallel
1130         # (e.g. takeip) and write multiple PIDs to the file.
1131         read _locker_event 
1132         if [ -n "$_locker_event" ] ; then
1133             while read _pid ; do
1134                 if [ -n "$_pid" -a "$_pid" != $$ ] && \
1135                     kill -0 "$_pid" 2>/dev/null ; then
1136                     exit 1
1137                 fi
1138             done
1139         fi
1140
1141         printf "%s\n%s\n" "$event_name" $$ >"$_lock"
1142         exit 0
1143     ) <"$_lock"
1144 }
1145
1146 ctdb_replay_monitor_status ()
1147 {
1148     echo "Replaying previous status for this script due to reconfigure..."
1149     # Leading colon (':') is missing in some versions...
1150     _out=$(ctdb scriptstatus -Y | grep -E "^:?monitor:${script_name}:")
1151     # Output looks like this:
1152     # :monitor:60.nfs:1:ERROR:1314764004.030861:1314764004.035514:foo bar:
1153     # This is the cheapest way of getting fields in the middle.
1154     set -- $(IFS=":" ; echo $_out)
1155     _code="$3"
1156     _status="$4"
1157     # The error output field can include colons so we'll try to
1158     # preserve them.  The weak checking at the beginning tries to make
1159     # this work for both broken (no leading ':') and fixed output.
1160     _out="${_out%:}"
1161     _err_out="${_out#*monitor:${script_name}:*:*:*:*:}"
1162     case "$_status" in
1163         OK) : ;;  # Do nothing special.
1164         TIMEDOUT)
1165             # Recast this as an error, since we can't exit with the
1166             # correct negative number.
1167             _code=1
1168             _err_out="[Replay of TIMEDOUT scriptstatus - note incorrect return code.] ${_err_out}"
1169             ;;
1170         DISABLED)
1171             # Recast this as an OK, since we can't exit with the
1172             # correct negative number.
1173             _code=0
1174             _err_out="[Replay of DISABLED scriptstatus - note incorrect return code.] ${_err_out}"
1175             ;;
1176         *) : ;;  # Must be ERROR, do nothing special.
1177     esac
1178     echo "$_err_out"
1179     exit $_code
1180 }
1181
1182 ctdb_service_check_reconfigure ()
1183 {
1184     [ -n "$1" ] || set -- "$service_name"
1185
1186     # We only care about some events in this function.  For others we
1187     # return now.
1188     case "$event_name" in
1189         monitor|ipreallocated|reconfigure) : ;;
1190         *) return 0 ;;
1191     esac
1192
1193     if ctdb_reconfigure_try_lock "$@" ; then
1194         # No events covered by this function are running, so proceed
1195         # with gay abandon.
1196         case "$event_name" in
1197             reconfigure)
1198                 (ctdb_service_reconfigure "$@")
1199                 exit $?
1200                 ;;
1201             ipreallocated)
1202                 if ctdb_service_needs_reconfigure "$@" ; then
1203                     ctdb_service_reconfigure "$@"
1204                 fi
1205                 ;;
1206             monitor)
1207                 if ctdb_service_needs_reconfigure "$@" ; then
1208                     ctdb_service_reconfigure "$@"
1209                     # Given that the reconfigure might not have
1210                     # resulted in the service being stable yet, we
1211                     # replay the previous status since that's the best
1212                     # information we have.
1213                     ctdb_replay_monitor_status
1214                 fi
1215                 ;;
1216         esac
1217     else
1218         # Somebody else is running an event we don't want to collide
1219         # with.  We proceed with caution.
1220         case "$event_name" in
1221             reconfigure)
1222                 # Tell whoever called us to retry.
1223                 exit 2
1224                 ;;
1225             ipreallocated)
1226                 # Defer any scheduled reconfigure and just run the
1227                 # rest of the ipreallocated event, as per the
1228                 # eventscript.  There's an assumption here that the
1229                 # event doesn't depend on any scheduled reconfigure.
1230                 # This is true in the current code.
1231                 return 0
1232                 ;;
1233             monitor)
1234                 # There is most likely a reconfigure in progress so
1235                 # the service is possibly unstable.  As above, we
1236                 # defer any scheduled reconfigured.  We also replay
1237                 # the previous monitor status since that's the best
1238                 # information we have.
1239                 ctdb_replay_monitor_status
1240                 ;;
1241         esac
1242     fi
1243 }
1244
1245 ##################################################################
1246 # Does CTDB manage this service? - and associated auto-start/stop
1247
1248 ctdb_compat_managed_service ()
1249 {
1250     if [ "$1" = "yes" -a "$2" = "$_service_name" ] ; then
1251         CTDB_MANAGED_SERVICES="$CTDB_MANAGED_SERVICES $2"
1252     fi
1253 }
1254
1255 is_ctdb_managed_service ()
1256 {
1257     _service_name="${1:-${service_name}}"
1258
1259     # $t is used just for readability and to allow better accurate
1260     # matching via leading/trailing spaces
1261     t=" $CTDB_MANAGED_SERVICES "
1262
1263     # Return 0 if "<space>$_service_name<space>" appears in $t
1264     if [ "${t#* ${_service_name} }" != "${t}" ] ; then
1265         return 0
1266     fi
1267
1268     # If above didn't match then update $CTDB_MANAGED_SERVICES for
1269     # backward compatibility and try again.
1270     ctdb_compat_managed_service "$CTDB_MANAGES_VSFTPD"   "vsftpd"
1271     ctdb_compat_managed_service "$CTDB_MANAGES_SAMBA"    "samba"
1272     ctdb_compat_managed_service "$CTDB_MANAGES_SCP"      "scp"
1273     ctdb_compat_managed_service "$CTDB_MANAGES_WINBIND"  "winbind"
1274     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "apache2"
1275     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "httpd"
1276     ctdb_compat_managed_service "$CTDB_MANAGES_ISCSI"    "iscsi"
1277     ctdb_compat_managed_service "$CTDB_MANAGES_CLAMD"    "clamd"
1278     ctdb_compat_managed_service "$CTDB_MANAGES_NFS"      "nfs"
1279     ctdb_compat_managed_service "$CTDB_MANAGES_NFS"      "nfs-ganesha-gpfs"
1280
1281     t=" $CTDB_MANAGED_SERVICES "
1282
1283     # Return 0 if "<space>$_service_name<space>" appears in $t
1284     [ "${t#* ${_service_name} }" != "${t}" ]
1285 }
1286
1287 ctdb_start_stop_service ()
1288 {
1289     # Do nothing unless configured to...
1290     [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] || return 0
1291
1292     _service_name="${1:-${service_name}}"
1293
1294     [ "$event_name" = "monitor" ] || return 0
1295
1296     if is_ctdb_managed_service "$_service_name" ; then
1297         if ! is_ctdb_previously_managed_service "$_service_name" ; then
1298             echo "Starting service \"$_service_name\" - now managed"
1299             background_with_logging ctdb_service_start "$_service_name"
1300             exit $?
1301         fi
1302     else
1303         if is_ctdb_previously_managed_service "$_service_name" ; then
1304             echo "Stopping service \"$_service_name\" - no longer managed"
1305             background_with_logging ctdb_service_stop "$_service_name"
1306             exit $?
1307         fi
1308     fi
1309 }
1310
1311 ctdb_service_start ()
1312 {
1313     # The service is marked managed if we've ever tried to start it.
1314     ctdb_service_managed "$@"
1315
1316     # Here we only want $1.  If no argument is passed then
1317     # service_start needs to know.
1318     service_start "$@" || return $?
1319
1320     ctdb_counter_init "$@"
1321     ctdb_check_tcp_init
1322 }
1323
1324 ctdb_service_stop ()
1325 {
1326     ctdb_service_unmanaged "$@"
1327     service_stop "$@"
1328 }
1329
1330 # Default service_start() and service_stop() functions.
1331  
1332 # These may be overridden in an eventscript.  When overriding, the
1333 # following convention must be followed.  If these functions are
1334 # called with no arguments then they may use internal logic to
1335 # determine whether the service is managed and, therefore, whether
1336 # they should take any action.  However, if the service name is
1337 # specified as an argument then an attempt must be made to start or
1338 # stop the service.  This is because the auto-start/stop code calls
1339 # them with the service name as an argument.
1340 service_start ()
1341 {
1342     service "${1:-${service_name}}" start
1343 }
1344
1345 service_stop ()
1346 {
1347     service "${1:-${service_name}}" stop
1348 }
1349
1350 ##################################################################
1351
1352 ctdb_standard_event_handler ()
1353 {
1354     case "$1" in
1355         status)
1356             ctdb_checkstatus
1357             exit
1358             ;;
1359         setstatus)
1360             shift
1361             ctdb_setstatus "$@"
1362             exit
1363             ;;
1364     esac
1365 }
1366
1367 # iptables doesn't like being re-entered, so flock-wrap it.
1368 iptables()
1369 {
1370         flock -w 30 $CTDB_VARDIR/iptables-ctdb.flock /sbin/iptables "$@"
1371 }
1372
1373 ########################################################
1374 # tickle handling
1375 ########################################################
1376
1377 # Temporary directory for tickles.
1378 tickledir="$CTDB_VARDIR/state/tickles"
1379 mkdir -p "$tickledir"
1380
1381 update_tickles ()
1382 {
1383         _port="$1"
1384
1385         mkdir -p "$tickledir" # Just in case
1386
1387         # Who am I?
1388         _pnn=$(ctdb pnn) ; _pnn=${_pnn#PNN:}
1389
1390         # What public IPs do I hold?
1391         _ips=$(ctdb -Y ip | awk -F: -v pnn=$_pnn '$3 == pnn {print $2}')
1392
1393         # IPs as a regexp choice
1394         _ipschoice="($(echo $_ips | sed -e 's/ /|/g' -e 's/\./\\\\./g'))"
1395
1396         # Record connections to our public IPs in a temporary file
1397         _my_connections="${tickledir}/${_port}.connections"
1398         rm -f "$_my_connections"
1399         netstat -tn |
1400         awk -v destpat="^${_ipschoice}:${_port}\$" \
1401           '$1 == "tcp" && $6 == "ESTABLISHED" && $4 ~ destpat {print $5, $4}' |
1402         sort >"$_my_connections"
1403
1404         # Record our current tickles in a temporary file
1405         _my_tickles="${tickledir}/${_port}.tickles"
1406         rm -f "$_my_tickles"
1407         for _i in $_ips ; do
1408                 ctdb -Y gettickles $_i $_port | 
1409                 awk -F: 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
1410         done |
1411         sort >"$_my_tickles"
1412
1413         # Add tickles for connections that we haven't already got tickles for
1414         comm -23 "$_my_connections" "$_my_tickles" |
1415         while read _src _dst ; do
1416                 ctdb addtickle $_src $_dst
1417         done
1418
1419         # Remove tickles for connections that are no longer there
1420         comm -13 "$_my_connections" "$_my_tickles" |
1421         while read _src _dst ; do
1422                 ctdb deltickle $_src $_dst
1423         done
1424
1425         rm -f "$_my_connections" "$_my_tickles" 
1426 }
1427
1428 ########################################################
1429 # load a site local config file
1430 ########################################################
1431
1432 [ -n "$CTDB_RC_LOCAL" -a -x "$CTDB_RC_LOCAL" ] && {
1433         . "$CTDB_RC_LOCAL"
1434 }
1435
1436 [ -x $CTDB_BASE/rc.local ] && {
1437         . $CTDB_BASE/rc.local
1438 }
1439
1440 [ -d $CTDB_BASE/rc.local.d ] && {
1441         for i in $CTDB_BASE/rc.local.d/* ; do
1442                 [ -x "$i" ] && . "$i"
1443         done
1444 }
1445
1446 # We'll call this here to ensure $CTDB_CURRENT_DEBUGLEVEL is set.
1447 # This gives us a chance to override the debug level using a file in
1448 # $CTDB_BASE/rc.local.d/.
1449 ctdb_set_current_debuglevel
1450
1451 script_name="${0##*/}"       # basename
1452 service_name="$script_name"  # default is just the script name
1453 service_fail_limit=1
1454 event_name="$1"