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