#!/bin/bash
#
#  Copyright Red Hat Inc., 2006
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version.
#
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to the
#  Free Software Foundation, Inc.,  675 Mass Ave, Cambridge, 
#  MA 02139, USA.
#
# Do reclaim-broadcasts when we kill lockd during shutdown/startup
# of a cluster service.
#
# Exported functions:
#
#  notify_list_store
#  notify_list_merge
#  notify_list_broadcast
#

#
# Usage:
# statd_notify <directory> <hostname|ip>
#
# Copy out a list from <directory>, merge them with the system nfs lock
# list, and send them out as <hostname|ip> after generating a random
# state (needed so clients will reclaim their locks)
#
nfslock_statd_notify()
{
	declare tmpdir=/tmp/statd-$2.$$
	declare nl_dir=$1
	declare nl_ip=$2
	declare command		# Work around bugs in rpc.statd
	declare pid_xxx		# Work around bugs in rpc.statd
	declare owner

	[ -z "$lockd_pid" ] && return 0
	if ! [ -d $nl_dir ]; then
		return 0
	fi

	if [ -z "`ls $nl_dir/sm/* 2> /dev/null`" ]; then
		ocf_log debug "No hosts to notify"
		return 0
	fi

	# Ok, copy the HA directory to something we can use.
	rm -rf $tmpdir
       	mkdir -p $tmpdir/sm
	
	# Copy in our specified entries
	cp -f $nl_dir/sm/* $tmpdir/sm

	# Copy in our global entries
	# XXX This might be what we just copied.

	if [ -d "/var/lib/nfs/statd/sm" ]; then
		owner=$(ls -dl /var/lib/nfs/statd/sm | awk '{print $3"."$4}')
		cp -f /var/lib/nfs/statd/sm/* $tmpdir/sm
	elif [ -d "/var/lib/nfs/sm" ]; then
		owner=$(ls -dl /var/lib/nfs/statd/sm | awk '{print $3"."$4}')
		cp -f /var/lib/nfs/sm/* $tmpdir/sm
	fi

	#
	# Generate a random state file.  If this ends up being what a client
	# already has in its list, that's bad, but the chances of this
	# are small - and relocations should be rare.
	#
	dd if=/dev/urandom of=$tmpdir/state bs=1 count=4 &> /dev/null

	#
	# Make sure we set permissions, or statd will not like it.
	#
	chown -R $owner $tmpdir

	#
	# Tell rpc.statd to notify clients.  Don't go into background, 
	# because statd is buggy and won't exit like it's supposed to after
	# sending the notifications out.
	#
	ocf_log info "Sending reclaim notifications via $nl_ip"
	command="rpc.statd -NFP $tmpdir -n $nl_ip"
	eval $command 2>&1 &
	sleep 3 # XXX - the instance of rpc.statd we just spawned is supposed
	        # to exit after it finishes notifying clients.
	        # rpc.statd spawned which is still running handles the actual
	        # new SM_MON requests... we hope 3 seconds is enough time
	        # to get all the SM_NOTIFY messages out.  rpc.statd = bugged
	#
	# clean up
	#
	pid_xxx=`ps auwwx | grep "$command" | grep -v grep | awk '{print $2}'`
	kill $pid_xxx
	rm -rf $tmpdir

	return 0
}


#
# Copy of isSlave from svclib_ip and/or ip.sh
#
nfslock_isSlave()
{
        declare intf=$1
        declare line

        if [ -z "$intf" ]; then
                ocf_log err "usage: isSlave <I/F>"
                return 1
        fi

        line=$(/sbin/ip link list dev $intf)
        if [ $? -ne 0 ]; then
                ocf_log err "$intf not found"
                return 1
        fi

        if [ "$line" = "${line/<*SLAVE*>/}" ]; then
                return 2
        fi

        # Yes, it is a slave device.  Ignore.
        return 0
}


#
# Get all the IPs on the system except loopback IPs
#
nfslock_ip_address_list()
{
        declare idx dev family ifaddr

        while read idx dev family ifaddr; do

		if [ "$family" != "inet" ] && [ "$family" != "inet6" ]; then
			continue
		fi

		if [ "$dev" = "lo" ]; then
			# Ignore loopback
			continue
		fi

                nfslock_isSlave $dev
                if [ $? -ne 2 ]; then
                        continue
                fi

                idx=${idx/:/}

                echo $dev $family ${ifaddr/\/*/} ${ifaddr/*\//}

        done < <(/sbin/ip -o addr list | awk '{print $1,$2,$3,$4}')

        return 0
}
	

#
# Usage: broadcast_notify <state_directory>
#
# Send the contents of <state_directory> out via all IPs on the system.
#
notify_list_broadcast()
{
        declare dev family addr maskbits ip_name
	declare lockd_pid=$(pidof lockd)
	declare nl_dir=$1

	# First of all, send lockd a SIGKILL.  We hope nfsd is running.
	# If it is, this will cause lockd to reset the grace period for
	# lock reclaiming.
	if [ -n "$lockd_pid" ]; then
		ocf_log info "Asking lockd to drop locks (pid $lockd_pid)"
		kill -9 $lockd_pid
	else
		ocf_log warning "lockd not running; cannot notify clients"
		return 1
	fi
	
        while read dev family addr maskbits; do
		if [ "$family" != "inet" ]; then
			continue
		fi

		ip_name=$(clufindhostname -i $addr)
		if [ -z "$ip_name" ]; then
			nfslock_statd_notify $nl_dir $addr
		else
			nfslock_statd_notify $nl_dir $ip_name
		fi

	done < <(nfslock_ip_address_list)
}


#
# Store the lock monitor list from rpc.statd - do this during a teardown
# after the IP addresses of a service have been taken offline.  Note that
# this should be done by HA-callout programs, but this feature is not in
# RHEL3. 
#
notify_list_store()
{
	declare nl_dir=$1
	declare owner

	mkdir -p $nl_dir/sm

	if [ -d "/var/lib/nfs/statd/sm" ]; then
	        if [ -z "`ls /var/lib/nfs/statd/sm/* 2> /dev/null`" ]; then
			return 1
			# nothing to do!
		fi

		owner=$(ls -dl /var/lib/nfs/statd/sm | awk '{print $3"."$4}')
		cp -af /var/lib/nfs/statd/sm/* $nl_dir/sm
		chown -R $owner $nl_dir
		return 0
	elif [ -d "/var/lib/nfs/sm" ]; then
	        if [ -z "`ls /var/lib/nfs/sm/* 2> /dev/null`" ]; then
			return 1
			# nothing to do!
		fi

		owner=$(ls -dl /var/lib/nfs/sm | awk '{print $3"."$4}')
		cp -af /var/lib/nfs/sm/* $nl_dir/sm
		chown -R $owner $nl_dir
		return 0
	fi

	return 1
}


#
# Merge the contents of <nl_dir>/sm with the system-wide list
# Make sure ownership is right, or statd will hiccup.  This should not
# actually ever be needed because statd will, upon getting a SM_MON
# request, create all the entries in this list.  It's mostly for
# housekeeping for next time we relocate the service.
#
notify_list_merge()
{
	declare nl_dir=$1
	declare owner

	if [ -z "`ls $nl_dir/* 2> /dev/null`" ]; then
		return 1
	fi

	if [ -d "/var/lib/nfs/statd/sm" ]; then
		owner=$(ls -dl /var/lib/nfs/statd/sm | awk '{print $3"."$4}')
 		cp -af $nl_dir/sm/* /var/lib/nfs/statd/sm
		chown -R $owner $nl_dir
		return 0
	elif [ -d "/var/lib/nfs/sm" ]; then
		owner=$(ls -dl /var/lib/nfs/sm | awk '{print $3"."$4}')
 		cp -af $nl_dir/sm/* /var/lib/nfs/sm
		chown -R $owner $nl_dir
		return 0
	fi

	return 1
}

