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