#!/bin/bash

#
#  Copyright Red Hat Inc., 2002
#  Copyright Mission Critical Linux, 2000
#
#  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.
#

# $Revision: 1.23 $
#
# Author: Gregory P. Myrdal <Myrdal@MissionCriticalLinux.Com>

#
# Shell library for filesystem functions
#

SH_LIB=$(dirname $0)

LIBRARIES=

for library in $LIBRARIES
do
	if [ -f $library ]; then
	  . $library
	fi
done

#
# mountInUse device mount_point
#
# Check to see if either the device or mount point are in use anywhere on
# the system.  It is not required that the device be mounted on the named
# moint point, just if either are in use.
#
mountInUse () {
	typeset mp tmp_mp
	typeset dev tmp_dev
	typeset junk

	if [ $# -ne 2 ]; then
		logAndPrint $LOG_ERR "Usage: mountInUse device mount_point".
		return $FAIL
	fi

	dev=$1
	mp=$2

	while read tmp_dev tmp_mp junk
	do
		if [ -n "$tmp_dev" -a "$tmp_dev" = "$dev" ]; then
			return $YES
		fi
		
		if [ -n "$tmp_mp" -a "$tmp_mp" = "$mp" ]; then
			return $YES
		fi
	done < <(mount | awk '{print $1,$3}')

	return $NO
}

#
# isMounted device mount_point
#
# Check to see if the device is mounted.  Print a warning if its not
# mounted on the directory we expect it to be mounted on.
#
isMounted () {

	typeset mp tmp_mp
	typeset dev tmp_dev

	if [ $# -ne 2 ]; then
		logAndPrint $LOG_ERR "Usage: isMounted device mount_point"
		return $FAIL
	fi

	dev=$1
	mp=$2
	
	while read tmp_dev tmp_mp ; do
		if [ -n "$tmp_dev" -a "$tmp_dev" = "$dev" ]; then
			#
			# Check to see if its mounted in the right
			# place
			#
			if [ -n "$tmp_mp"  -a "$tmp_mp"  != "$mp" ]; then
				logAndPrint $LOG_WARNING "\
Device $dev is mounted on $tmp_mp instead of $mp"
			fi
			return $YES
		fi
	done < <(mount | awk '{print $1,$3}')

	return $NO
}


#
# killMountProcesses device mount_point
#
# Using lsof or fuser try to unmount the mount by killing of the processes
# that might be keeping it busy.
#
killMountProcesses()
{
	typeset have_lsof=""
	typeset have_fuser=""
	typeset -i try

	if [ $# -ne 2 ]; then
	  logAndPrint $LOG_ERR "Usage: killMountProcesses device mount_point"
	  return $FAIL
	fi

	typeset -i ret=$SUCCESS
	typeset dev=$1
	typeset mp=$2

	logAndPrint $LOG_INFO "Forcefully unmounting $dev ($mp)"

	#
	# Not all distributions have lsof.  If not use fuser.  If it
	# does, try both.
  	#
	file=$(which lsof 2>/dev/null)
	if [ -f "$file" ]; then
	  have_lsof=$YES
	fi

	file=$(which fuser 2>/dev/null)
	if [ -f "$file" ]; then
	  have_fuser=$YES
	fi             

	if [ -z "$have_lsof" -a -z "$have_fuser" ]; then
	  logAndPrint $LOG_WARNING \
	  "Cannot forcefully unmount $dev; cannot find lsof or fuser commands"
	  return $FAIL
	fi
		
	for try in 1 2 3
	do
	  if [ -n "$have_lsof" ]; then
	    #
	    # Use lsof to free up mount point
	    #
	    while read command pid user
	    do
	      if [ -z "$pid" ]; then
	        continue
	      fi

	      if [ $try -eq 1 ]; then
	        logAndPrint $LOG_WARNING \
	                    "killing process $pid ($user $command $dev)"
	      elif [ $try -eq 3 ]; then
		logAndPrint $LOG_CRIT \
		    "Could not clean up mount $dev (attached to $mp)"
		ret=$FAIL
 	      fi

	      if [ $try -gt 1 ]; then
	        kill -9 $pid
	      else
	        kill -TERM $pid
	      fi
	    done < <(lsof -bn 2>/dev/null | \
		     grep -E "$mp(/.*|)\$" | \
	             awk '{print $1,$2,$3}' | \
	             sort -u -k 1,3)
	  elif [ -n "$have_fuser" ]; then
	    #
	    # Use fuser to free up mount point
	    #
	    while read command pid user
	    do
	      if [ -z "$pid" ]; then
	        continue
	      fi

	      if [ $try -eq 1 ]; then
	        logAndPrint $LOG_WARNING \
	                    "killing process $pid ($user $command $dev)"
	      elif [ $try -eq 3 ]; then
		logAndPrint $LOG_CRIT \
		    "Could not clean up mount $dev (attached to $mp)"
		ret=$FAIL
 	      fi

	      if [ $try -gt 1 ]; then
	        kill -9 $pid
	      else
	        kill -TERM $pid
	      fi
	    done < <(fuser -vm $dev | \
                     grep -v PID | \
	             sed 's;^'$dev';;' | \
                     awk '{print $4,$2,$1}' | \
                     sort -u -k 1,3)
	  fi
	done

	return $ret
}


#
# Enable quotas on the mount point if the user requested them
#
enable_fs_quotas()
{
	declare -i need_check=0
	declare quotaopts=""
	declare mopt
	declare opts=$1
	declare mp=$2


	for mopt in `echo $opts | sed -e s/,/\ /g`; do
		case $mopt in
		usrquota)
			quotaopts="u$quotaopts"
			continue
			;;
		grpquota)
			quotaopts="g$quotaopts"
			continue
			;;
		noquota)
			quotaopts=""
			return 0
			;;
		esac
	done

	[ -z "$quotaopts" ] && return 0

	# Ok, create quota files if they don't exist
	for f in quota.user aquota.user quota.group aquota.group; do
		if ! [ -f "$mp/$f" ]; then
			logAndPrint $LOG_INFO "$mp/$f was missing - creating"
			touch "$mp/$f" 
			chmod 600 "$mp/$f"
			need_check=1
		fi
	done

	if [ $need_check -eq 1 ]; then
		logAndPrint $LOG_INFO "Checking quota info in $mp"
		quotacheck -$quotaopts $mp
	fi

	logAndPrint $LOG_DEBUG "quotaon -$quotaopts $mp"
	quotaon -$quotaopts $mp

	return $?
}



#
# startFilesystem serviceID deviceToken device mountpoint
#
startFilesystem() {
	typeset -i ret_val=$SUCCESS
	typeset mp=""			# mount point
	typeset dev=""			# device
	typeset fstype=""
	typeset opts=""
	typeset device_in_use=""
	typeset mount_options=""

	if [ $# -ne 4 ]; then
	  logAndPrint $LOG_ERR "Usage: startFilesystem serviceID token device mountpoint"
	fi

	typeset svcID=$1
	typeset token=$2
	typeset dev=$3
	typeset mp=$4
	typeset svc_name=$(getSvcName $DB $svcID)
	typeset nfs_lock_workaround=$(getSvcMgrNFSLock $DB)

	#
	# Validate mount point If not, no need to continue.
	#
	case "$mp" in 
	""|"[ 	]*")
	  return $SUCCESS	# nothing to mount
	  ;;
	/*) :			# found it
	  ;;
	*)	 		# invalid format
	  logAndPrint $LOG_ERR \
"startFilesystem: Invalid mount point format (must begin with a '/'): \'$mp\'"
	  return $FAIL
	  ;;
	esac
	
	#
	# Ensure we've got a valid directory
	#
	if [ -e "$mp" ]; then
		if ! [ -d "$mp" ]; then
			logAndPrint $LOG_ERR "\
startFilesystem: Mount point $mp exists but is not a directory"
			return $FAIL
		fi
	else
		logAndPrint $LOG_INFO "\
startFilesystem: Creating mount point $mp for device $dev"
		mkdir -p $mp
	fi

	#
	# Get the filesystem type, if specified.
	#
	fstype_option=""
	fstype=$(getSvcMountFstype $DB $token)
	case $? in 
	0) 
	  case "$fstype" in 
	  ""|"[ 	]*")
	    fstype="";;				# clugetconfig returns white space
	  *)  fstype_option="-t $fstype" ;;	# found it
	  esac
	  ;;
	2) fstype="";;	   # clugetconfig returns "not found", erase it 
	*) logAndPrint $LOG_ERR "\
startFilesystem: Error getting FS type. Device: $token (service $svc_name), err=$?"
	  return $FAIL ;;
        esac

	#
	# See if the device is already mounted.
	# 
	isMounted $dev $mp
	case $? in
	$YES)				# already mounted
	  logAndPrint $LOG_DEBUG "$dev already mounted"
	  return $SUCCESS
	  ;;
	$NO) : ;;			# not mounted, continue
	$FAIL) return $FAIL ;;
	esac

	#
	# Make sure that neither the device nor the mount point are mounted
	# (i.e. they may be mounted in a different location).  The'mountInUse'
	# function checks to see if either the device or mount point are in
	# use somewhere else on the system.
	#
	mountInUse $dev $mp
	case $? in
	$YES)		# uh oh, someone is using the device or mount point
	  logAndPrint $LOG_ERR "\
Cannot mount $dev on $mp, the device or mount point is already in use!"
	  return $FAIL
	  ;;
	$NO) : ;;	# good, no one else is using it
	$FAIL)
	  return $FAIL
	  ;;
	*)
	  logAndPrint $LOG_ERR "Unknown return from mountInUse"
	  return $FAIL
	  ;;
	esac

	#
	# Make sure the mount point exists.
	#
	if [ ! -d $mp ]; then
	  rm -f $mp			# rm in case its a plain file
	  mkdir -p $mp			# create the mount point
	  ret_val=$?
	  if [ $ret_val -ne 0 ]; then
	    logAndPrint $LOG_ERR "'mkdir -p $mp' failed, error=$ret_val"
	    return $FAIL
	  fi
	fi

	#
	# Get the mount options, if they exist.
	#
	mount_options=""
	opts=$(getSvcMountOptions $DB $token)
	case $? in
	0) 
	  case "$opts" in 
	  ""|"[ 	]*")
	    opts="";;				# clugetconfig returns white space
	  *) mount_options="-o $opts" ;;	# found it
	  esac
	  ;;
	2) opts="";;		# clugetconfig returns "not found", erase it 
	*) logAndPrint $LOG_ERR "\
startFilesystem: Invalid mount options $token (service $svc_name), err=$?"
	  return $FAIL ;;
	esac

	#
	# Check to determine if we need to fsck the filesystem.
	#
	# Note: this code should not indicate in any manner suggested
	# file systems to use in the cluster.  Known filesystems are
	# listed here for correct operation.
	#
        case "$fstype" in
        reiserfs) typeset fsck_needed="" ;;
        ext3)     typeset fsck_needed="" ;;
        jfs)      typeset fsck_needed="" ;;
        xfs)      typeset fsck_needed="" ;;
	gfs)	  typeset fsck_needed="" ;;
	vxfs)	  typeset fsck_needed="" ;;
        ext2)     typeset fsck_needed=yes ;;
        minix)    typeset fsck_needed=yes ;;
        vfat)     typeset fsck_needed=yes ;;
        msdos)    typeset fsck_needed=yes ;;
	"")       typeset fsck_needed=yes ;;		# assume fsck
	*)        typeset fsck_needed=yes 		# assume fsck
	          logAndPrint $LOG_WARNING "\
Unknown file system type '$fstype' for device $dev.  Assuming fsck is required."
	          ;;
	esac

	#
	# Fsck the device, if needed.
	#
	if [ -n "$fsck_needed" ]; then
	  typeset fsck_log=/tmp/$(basename $dev).fsck.log
	  logAndPrint $LOG_DEBUG "Running fsck on $dev"
	  fsck -p $dev >> $fsck_log 2>&1
	  ret_val=$?
	  if [ $ret_val -gt 1 ]; then
	    logAndPrint $LOG_ERR "\
'fsck -p $dev' failed, error=$ret_val; check $fsck_log for errors"
	    logAndPrint $LOG_DEBUG "Invalidating buffers for $dev"
	    $INVALIDATEBUFFERS -f $dev
	    return $FAIL
	  fi
	  rm -f $fsck_log
	fi

	#
	# Mount the device
	#
	logAndPrint $LOG_DEBUG "mount $fstype_option $mount_options $dev $mp"
	mount $fstype_option $mount_options $dev $mp
	ret_val=$?
	if [ $ret_val -ne 0 ]; then
	  logAndPrint $LOG_ERR "\
'mount $fstype_option $mount_options $dev $mp' failed, error=$ret_val"
	  return $FAIL
	fi

	#
	# Create this for the NFS NLM broadcast bit
	#
	if [ "$nfs_lock_workaround" = "yes" ] || \
	   [ "$nfs_lock_workaround" = "1" ]; then
		mkdir -p $mp/.clumanager/statd
		notify_list_merge $mp/.clumanager/statd
	fi

	enable_fs_quotas $opts $mp
	
	return $SUCCESS
}

#
# stopFilesystem serviceID token device mountpoint
#
# Run the stop actions
#
stopFilesystem() {
	typeset -i ret_val=0
	typeset -i try=1
	typeset -i max_tries=3		# how many times to try umount
	typeset -i sleep_time=2		# time between each umount failure
	typeset done=""
	typeset umount_failed=""
	typeset force_umount=""
	typeset fstype=""
	typeset nfs_lock_workaround=$(getSvcMgrNFSLock $DB)

	if [ $# -ne 4 ]; then
	  logAndPrint $LOG_ERR "Usage: stopFilesystem serviceID token device mountpoint"
	  return $FAIL
	fi

	typeset svcID=$1
	typeset token=$2
	typeset dev=$3
	typeset mp=$4
	typeset svc_name=$(getSvcName $DB $svcID)
	typeset nfs_svc

	#
	# See if this is an NFS service
	#
	is_nfs_service $svcID
	nfs_svc=$?

	#
	# Get the file system type...
	#
	fstype=$(getSvcMountFstype $DB $token)
	case $? in 
	0) 
	  case "$fstype" in 
	  ""|"[ 	]*")
	    fstype="";;				# clugetconfig returns white space
	  *) ;;	# found it
	  esac
	  ;;
	2) fstype="";;	   # clugetconfig returns "not found", erase it 
	*) logAndPrint $LOG_ERR "\
startFilesystem: Error getting FS type.  Device: $token (service $svc_name), err=$?"
	  return $FAIL ;;
        esac

	#
	# Get the force unmount setting if there is a mount point.
	#
	if [ -n "$mp" ]; then
	  force_umount=$(getSvcForceUnmount $DB $token)
	  case $? in
	    0) 				# found it
	      case $force_umount in
	        $YES_STR)	force_umount=$YES ;;
	        *)		force_umount="" ;;
	      esac
	      ;;
	    2) force_umount="" ;;	# no force umount set
	    *) logAndPrint $LOG_ERR "\
stopFilesystem: Error getting force umount flag. Device: $token (service $svc_name), err=$?"
	       return $FAIL ;;
	  esac
	fi

	#
	# Unmount the device.  
	#
	# If we fail, try to forcefully unmount the filesystem by killing any
	# processes using that file system (The poor man's forced unmount).
	#
	# In the case of Clustered File Systems, don't unmount unless
	# 'force unmount' is set.
	#
	case $fstype in
	  gfs)
	    if [ -z "$force_unmount" ]; then
	      return $SUCCESS
            fi
	    ;;
          *) ;;
        esac

	quotaoff $mp &> /dev/null
	while [ ! "$done" ]; do
	  isMounted $dev $mp
	  case $? in
	  $NO)
	    logAndPrint $LOG_INFO "$dev is not mounted"
	    umount_failed=
	    done=$YES
	    ;;
	  $FAIL)
	    return $FAIL
	    ;;
	  $YES)
	    sync; sync; sync
	    logAndPrint $LOG_INFO "unmounting $dev ($mp)"

	    umount $dev
	    let ret_val=$?
	    if  [ $ret_val -eq 0 ]; then
	      #
	      # The Linux kernel will not invalidate buffers
	      # after an unmount.  Since this disk might come
	      # back with different data on the disk we want
	      # to make sure that the system buffers are
	      # invalidated before it goes.  NOTE: this is
	      # fixed in the 2.4 kernel and all calls to
	      # $INVALIDATEBUFFERS can be removed when all
	      # clusters are running this version of the
	      # kernel.
	      #
	      logAndPrint $LOG_DEBUG "Invalidating buffers for $dev"
	      $INVALIDATEBUFFERS -f $dev

	      umount_failed=
	      done=$YES
	      continue
	    fi

	    umount_failed=yes

	    if [ "$force_umount" ]; then
	      killMountProcesses $dev $mp
	      if [ $try -eq 1 ] && [ $nfs_svc -eq $YES ]; then
	        if [ "$nfs_lock_workaround" = "yes" ] || \
	           [ "$nfs_lock_workaround" = "1" ]; then
                  logAndPrint $LOG_WARNING \
		     "Dropping node-wide NFS locks"

		  pkill -KILL -x lockd
	          mkdir -p $mp/.clumanager/statd

	          # Copy out the notify list; our IPs are already torn down
	          if notify_list_store $mp/.clumanager/statd; then
	            notify_list_broadcast $mp/.clumanager/statd
                  fi
                fi
	      fi
	    fi

	    if [ $try -ge $max_tries ]; then
	      done=$YES
	    else
	      sleep $sleep_time
	      let try=try+1
	    fi
	    ;;
	  *)
	    return $FAIL
	    ;;
	  esac

	  if [ $try -ge $max_tries ]; then
	    done=$YES
	  else
	    sleep $sleep_time
	    let try=try+1
	  fi

	done

	if [ -n "$umount_failed" ]; then
	  logAndPrint $LOG_ERR "'umount $dev' failed ($mp), error=$ret_val"
	  return $FAIL
        fi

	return $SUCCESS
}

#
# startFilesystems serviceID
#
startFilesystems()
{
	typeset dev
	typeset -i ret_val
	typeset mp mount_point
	typeset tokenlist token

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: startFilesystems serviceID"
	  return $FAIL
	fi

	typeset svcID=$1

	#
	# Get all mounts points in the correct heirarchical order.
	# Save the mount point ID so we can grab other associated
	# attributes for that mount later.
	#
	tokenlist=$(getSvcDeviceTokenList $DB $svcID)
	for token in $tokenlist; do

	  #
	  # We loop on devices looking for devices that have mount points
	  #
	  dev=$(getSvcDevice $DB $token)
	  case $? in
	    0) : ;;		# found it, let it fall through
	    2) break ;;		# done getting devices, no more left
	    *) logAndPrint $LOG_ERR "\
startFilesystems: Error getting device name. Device: $token (service $svc_name) err=$?"
	       return $FAIL;;
	  esac

	  mp=$(getSvcMountPoint $DB $token)
	  case $? in
	    0) : ;;			# found a mount point
	    2) # no mount point, go to next device
	       continue ;;
	    *) logAndPrint $LOG_ERR "\
startFilesystems: Error getting mount point. Device: $token (service $svc_name) err=$?"
	       return $FAIL ;;
	  esac

	  startFilesystem $svcID $token $dev $mp || return $FAIL
	done

	return $SUCCESS
}

#
# stopFilesystems serviceID
#
stopFilesystems()
{
	typeset dev
	typeset mp mount_point
	typeset -i ret_val
	typeset token tokenlist

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: stopFilesystems serviceID"
	  return $FAIL
	fi

	typeset svcID=$1
	typeset svc_name=$(getSvcName $DB $svcID)

	#
	# Get all mounts points in the correct heirarchical order.
	# Save the mount point ID so we can grab other associated
	# attributes for that mount later.
	#
	tokenlist=$(getSvcDeviceTokenList_r $DB $svcID)
	for token in $tokenlist; do
	  #
	  # We loop on devices looking for devices that have mount points
	  #
	  dev=$(getSvcDevice $DB $token)
	  case $? in
	    0) : ;;		# found it, let it fall through
	    2) break ;;		# done getting devices, no more left
	    *) logAndPrint $LOG_ERR "\
stopFilesystems: Error getting device name. Device: $token (service $svc_name), err=$?"
	       return $FAIL;;
	  esac

	  mp=$(getSvcMountPoint $DB $token)
	  case $? in
	    0) : ;;			# found a mount point
	    2) # no mount point, go to next device
	       continue ;;
	    *) logAndPrint $LOG_ERR "\
stopFilesystems: Error getting mount point. Device: $token (service $svc_name), err=$?"
	       return $FAIL ;;
	  esac

	  stopFilesystem $svcID $token $dev $mp || return $FAIL
	done

	return $SUCCESS
}

#
# statusFilesystems <svcid>
#
statusFilesystems()
{
	typeset dev
	typeset svcID=$1
	typeset mp mount_point
	typeset -i ret_val
	typeset token tokenlist

	if [ $# -ne 1 ]; then
	  logAndPrint $LOG_ERR "Usage: statusFilesystems serviceID"
	  return $FAIL
	fi

	typeset svcID=$1
	typeset svc_name=$(getSvcName $DB $svcID)

	#
	# Get all mounts points in the correct heirarchical order.
	# Save the mount point ID so we can grab other associated
	# attributes for that mount later.
	#
	tokenlist=$(getSvcDeviceTokenList $DB $svcID)
	for token in $tokenlist; do
	  #
	  # We loop on devices looking for devices that have mount points
	  #
	  dev=$(getSvcDevice $DB $token)
	  case $? in
	    0) : ;;		# found it, let it fall through
	    2) break ;;		# done getting devices, no more left
				# Should never happen with new model.
	    *) logAndPrint $LOG_ERR "\
stopFilesystems: Error getting device name. Device: $token (service $svc_name), err=$?"
	       return $FAIL;;
	  esac

	  mp=$(getSvcMountPoint $DB $token)
	  case $? in
	    0) : ;;			# found a mount point
	    2)# no mount point, go to next device
	       continue ;;
	    *) logAndPrint $LOG_ERR "\
stopFilesystems: Error getting mount point. Device: $token (service $svc_name), err=$?"
	       return $FAIL ;;
	  esac

	  isMounted $dev $mp
	  case $? in
	  $YES)	: ;;
	  $NO)
	    logAndPrint $LOG_ERR "$dev is not mounted on $mp"
	    return $FAIL ;;
	  $FAIL)
	    return $FAIL ;;
	  esac
	done

	return $SUCCESS
}

#
# filesystem action serviceID
#
filesystem()
{
	if [ $# -ne 2 ]; then
	  logAndPrint $LOG_ERR \
	              "Usage: filesystem [start, stop, status] serviceID"
	  return $FAIL
	fi

	typeset action=$1
	typeset svcID=$2

	case "$action" in
	'start')
	  startFilesystems $svcID
	  return $?
	  ;;

	'stop')
	  stopFilesystems $svcID
	  return $?
	  ;;

	'status')
	  statusFilesystems $svcID
	  return $?
	  ;;

	*)
	  logAndPrint $LOG_ERR \
	            "filesystem: Cannot decipher action argument \'$action\'"
	  return $FAIL
	  ;;
	esac
}
