#!/bin/sh
#
# Update server ${EXPORT_FILE} file to include diskless client directories
#
# Usage:
#	update_exports [-x] [-d] [client] [client_root_directory] \
#		[client_swap_file] [client_home_directory] \
#		[client_exec_directory] [client_kvm_directory]
#
#
# Tries to handle most cases, including symbolic links to directories
# that may or may not already have entries in the exports file.
#
# Copyright (c) 1989 Solbourne Computer, Inc.


#
# Global variables
#

Myname=`basename "$0"`
Usage="[-x] [-d] [client] [client_root_directory] [client_swap_file] [client_home_directory] [client_exec_directory] [client_kvm_directory]"
Delete=""

#
# Filesystem specific pathnames
#

EXPORT_DIR=${EXPORT_DIR-/export}
EXPORT_USR=${EXPORT_USR-${EXPORT_DIR}/exec}
EXPORT_FILE=${EXPORT_FILE-/etc/exports}
LOG=${LOG-/var/log/diskless_log}
MYPATH=${MYPATH-/usr/etc/setup}
SERVER_USR=${SERVER_USR-/usr}
SERVER_HOME=${SERVER_HOME-/home}
SERVER_SHARE=${SERVER_SHARE-${EXPORT_DIR}/share}
SERVER_CRASH=${SERVER_CRASH-${EXPORT_DIR}/crash}
TMP_ERR=/tmp/ins_err$$
TMP_EXP=/tmp/ins_exp$$

#
# Set up default path
#

PATH=/bin:/usr/bin:/usr/ucb:${MYPATH}
export PATH

#
# Main function - test arguments and verify valid call
#
main()
{

	# Get optional arguments

	while getopts "dx" arg
	do
		case "$arg" in
		d)
			Delete=-d;;
		x)
			set -x;;
		*)
			usage ;;
		esac
	done

	shift `expr $OPTIND - 1`

	# Test for proper remaining arguments

	if [ $# -gt 6 ]
	then
		usage
	fi

	if [ "$Delete" = "-d" -a $# -lt 1 ]
	then
		error "Must specify client name to delete"
		usage
	fi

	# Arguments okay.  Record start in log and catch signals

	log "$Delete $* Started `date`"
	trap "sig_abort; wrapup 1" 1 2 3 15

	# Do real work in a sub-shell, to capture error output for log
	# Redirect standard input to /dev/null, just in case something
	# tries to read.

	(
		do_cmd ${1+"$@"} 2>&1
	) </dev/null | tee ${TMP_ERR}

	# Record any error output to log, and display to user
	# If there was an error, pass exit status up

	cmp -s ${TMP_ERR} /dev/null
	wrapup $?
}

#
# Verify client and/or server directories, and do update
#

do_cmd()
{
	trap 1 2 3 15

	CLIENT=${1-""}

	if [ "${CLIENT}" != "" ]
	then
		CLIENT_ROOT=${2-${EXPORT_DIR}/root/$CLIENT}
		CLIENT_SWAP=`dirname ${3-${EXPORT_DIR}/swap/${CLIENT}/swap.${CLIENT}}` || exit 1
		CLIENT_HOME=${4-${CLIENT_ROOT}/home}
		CLIENT_CRASH=""
		CLIENT_USR=${5-${EXPORT_USR}}
		CLIENT_KVM=${6-${EXPORT_USR}/kvm}
	else

		# If called with no client name, do server directories

		CLIENT_ROOT=${SERVER_USR}
		CLIENT_SWAP=${SERVER_HOME}
		CLIENT_HOME=${SERVER_SHARE}
		if [ -d ${SERVER_CRASH} ]
		then
			CLIENT_CRASH=${SERVER_CRASH}
		else
			CLIENT_CRASH=""
		fi
		CLIENT_USR=${EXPORT_USR}
		CLIENT_KVM=${EXPORT_USR}/kvm
	fi
	
	# Get real names of directories by using /bin/pwd.  This
	# allows "/export", "/export/root", "/export/root/<client>",
	# or whatever, to be a symbolic link to some other file
	# system.
	
	CLIENT_ROOT=`get_realname "${CLIENT_ROOT}"` || exit 1
	CLIENT_SWAP=`get_realname "${CLIENT_SWAP}"` || exit 1
	CLIENT_KVM=`get_realname "${CLIENT_KVM}"` || exit 1

	# Check home directory

	if [ "`expr ${CLIENT_HOME} : '..*\(:\)'`" != ":" ]
	then
		CLIENT_HOME=`get_realname "${CLIENT_HOME}"` || exit 1
	else
		CLIENT_HOME=""
	fi

	CLIENT_CRASH=`get_realname "${CLIENT_CRASH}"` || exit 1
	CLIENT_USR=`get_realname "${CLIENT_USR}"` || exit 1

	# Create a copy of the original exports file and do edits on it

	if [ -f ${EXPORT_FILE} ]
	then
		cp ${EXPORT_FILE} ${TMP_EXP} || exit 1
	else
		touch ${TMP_EXP} || exit 1
	fi

	# Scan current exports file to see if our directories
	# or parents of our directories are being exported
	# If not, just add a new entry; otherwise, add
	# client permissions to exported directory name

	for i in ${CLIENT_ROOT} ${CLIENT_SWAP} ${CLIENT_HOME} ${CLIENT_CRASH} \
			${CLIENT_USR} ${CLIENT_KVM}
	do
		# If directory is nfs-mounted, don't try to export it

		if ( remote_mount ${i} )
		then
			continue
		fi

		Name=`get_exportname "$i"` || exit 1

		if [ "$Name" = "" ]
		then
			add_entry "$i"
		else
			update_entry "$Name"
		fi
	done

	# Temporary file updated.  Copy back to original EXPORT_FILE

	cp ${TMP_EXP} ${EXPORT_FILE} || exit 1
}

#
# Get real directory name instead of symbolic link name
# by changing to the directory and using "/bin/pwd"
#

get_realname()
{
	Dir="$1"

	if [ "$Dir" = "" ]
	then
		return
	fi

	if [ ! -d "$Dir" ]
	then
		# If $Dir isn't there, we have to believe the name as is.
		echo $Dir
	else
		cd $Dir
		/bin/pwd
	fi
}

#
# Decide whether given directory is below target directory
# (e.g. /usr/export/exec is below /usr, but not below /usr1)
#

isbelow()
{
	given="$1"
	target="$2"
	while true
	do
		if [ "${given}" = "/" -o "${given}" = "." ]
		then
			return 1
		fi
		if [ "${given}" = "${target}" ]
		then
			return 0
		fi
		given=`dirname "${given}"`
	done
}

#
# Scan /etc/exports file to see if a sub-directory, or parents
# of our directory, is being exported already.  If so,
# return exported directory name
#

get_exportname()
{
	Dir="${1}"
	Fstab="no"
	Export=""

	for i in `sed -e 's/#.*//' -e 's/^[ 	]//' -e 's/[ 	].*//'  <${TMP_EXP}`
	do
		if [ "$i" != "/" ]
		then
			if isbelow "${Dir}" "$i"
			then
				Export=$i
			elif isbelow "$i" "${Dir}"
			then
			# Subdirectory of the desired directory is being exported
			# Change name to parent
				ed - ${TMP_EXP} <<-_EOF_
					H
					g?${i}?s??${Dir}?
					w
					q
				_EOF_
				Export=$Dir
			else
				Export=${Export}
			fi
		else
			Fstab="yes"
		fi
	done

	# If the root directory is being exported, and we did not find
	# our directory, then we must scan /etc/fstab to see if
	# our directory is in mounted file systems.

	if [ "$Fstab" = "yes" ]
	then
		for i in `awk '{print $2}' </etc/fstab`
		do
			if [ "$i" != "/" ]
			then
				expr "${Dir}" : "$i" >/dev/null 2>&1 \
					&& Mount=$i || Mount=${Mount}
			fi
		done

		# If directory was not found in exports, and it was
		# not found in fstab, then it is assumed to be
		# in root.  Set name to "/", since it is known to
		# be exported

		if [ "$Export" = "" -a "$Mount" = "" ]
		then
			Export="/"
		fi
	fi
	echo "${Export}"
}

#
# Directory is being exported at a higher level.  Attempt to add
# -root=<client> -access=<client> entries as appropriate,
# after removing any previous entries for <client>.  This sure is messy!!
#
#
# Editor substitute commands handle the following (in order):
#     Simple case - "name:<client>:name" becomes "name:name"
#     Simple case: ":<client>" (and white space) at end of line is removed
#     Client name followed by delimiter is replaced with delimiter
#     Client name begins option string, other names follow: -root=<client>:name
#     Entry "-root=<client>" followed by delimiter deleted
#     Entry "-access=<client>" followed by delimiter deleted
#     Entry "rw=<client>" followed by delimiter is replaced with "ro"
#     Entry "-root=<client>" (and white space) ends line
#     Entry "-access=<client>" (and white space) ends line
#     Entry "rw=<client>" (and white space) is replaced with "ro"	
#     White space at end of line is removed
#
# Note that the directory argument name can be simply "/"!!

update_entry()
{

	# If updating servers info, no client name to add
	if [ "${CLIENT}" = "" ]
	then
		return
	fi

	Dir="$1"

	# If client name is already specified, remove it


	if grep "$Dir[ 	].*${CLIENT}" <${TMP_EXP} >/dev/null
	then
		ed - ${TMP_EXP} <<-_EOF_
			H
			g?$Dir[ 	]?s?:${CLIENT}:?:?g
			g?$Dir[ 	]?s?:${CLIENT}[ 	]*\$??
			g?$Dir[ 	]?s?:${CLIENT}\([, 	]*\)?\1?g
			g?$Dir[ 	]?s?=${CLIENT}:?=?g
			g?$Dir[ 	]?s?root=${CLIENT}[ 	]*,??
			g?$Dir[ 	]?s?access=${CLIENT}[ 	]*,??
			g?$Dir[ 	]?s?rw=${CLIENT}[ 	]*,?ro,?
			g?$Dir[ 	]?s?[ 	]*[,-]root=${CLIENT}[ 	]*\$??
			g?$Dir[ 	]?s?[ 	]*[,-]access=${CLIENT}[ 	]*\$??
			g?$Dir[ 	]?s?\([ 	]*[,-]\)rw=${CLIENT}[ 	]*\$?\1ro?
			g?$Dir[ 	]?s?$i[ 	]*\$?$i?
			w
			q
		_EOF_
	fi

	# If "-d" flag was specified, delete any reference to
	# client root and/or swap directories and return

	if [ "$Delete" = "-d" ]
	then
		Swap=`get_realname "${EXPORT_DIR}/swap"`
		ed - ${TMP_EXP} <<-_EOF_
			H
			g?${CLIENT_ROOT}?d
			g?${Swap}/${CLIENT}?d
			w
			q
			.
		_EOF_
		return
	fi

	# Now add -root and -access permissions for <client>
	# If the options are already specified on the line, just
	# add client to the beginning.  If the option is not
	# specified, add it.

	for option in "root=" "access="
	do
		if grep "$Dir[ 	].*$option" <${TMP_EXP} >/dev/null
		then
			ed - ${TMP_EXP} <<-_EOF_
				H
				g?$Dir[ 	]?s,$option,&${CLIENT}:,
				w
				q
			_EOF_
		else
			ed - ${TMP_EXP} <<-_EOF_
				H
				g?$Dir\$?s?\$?	-?
				g?$Dir[ 	]?s?\$?,$option${CLIENT}?
				g?$Dir[ 	]-,?s?-,?-?
				w
				q
			_EOF_
		fi
	done

	# If it is rw=name, convert it to rw=<client>:name.
	# If filesystem is ro, convert it to rw=<client>.
	# If it is neither ro nor rw, we can just leave it alone (implicit rw)

	if grep "$Dir[ 	].*rw=" <${TMP_EXP} >/dev/null
	then
		ed - ${TMP_EXP} <<-_EOF_
			H
			g?$Dir[ 	]?s?rw=?rw=${CLIENT}:?
			w
			q
		_EOF_
	elif grep "$Dir[ 	].*[,-]ro" <${TMP_EXP} >/dev/null
	then
		# beware: don't confuse "ro" with "root"
		ed - ${TMP_EXP} <<-_EOF_
			H
			g?$Dir[ 	]?s?\([,-]\)ro\$?\1rw=${CLIENT}?
			g?$Dir[ 	]?s?\([,-]\)ro\([^o]\)?\1rw=${CLIENT}\2?
			w
			q
		_EOF_
	fi
}

#
# Add new entry for our directory in exports file
#

add_entry()
{
	Dir="$1"

	# If "-d" flag was specified, do not do anything

	if [ "$Delete" = "-d" ]
	then
		return
	fi

	# If updating server directories, just add directory name

	if [ "${CLIENT}" = "" ]
	then
		Line="$Dir"
	else
		Line="$Dir	-root=${CLIENT},access=${CLIENT}"
	fi

	echo "$Line" >>${TMP_EXP}
}

#
# Determine if a directory is remote-mounted.
#

remote_mount()
{
	# The reason for scanning both /etc/fstab and /etc/mtab is
	# to catch the automounter doing things behind our back.  This
	# is not a complete solution -- if the automounter isn't running,
	# but *is* mounting a particular directory for us, we lose.  Not
	# much that can be done about it, since in the general case it
	# is impractical to attempt to determine what the automounter will
	# do on any given system.
 
	# Due to an oddity with "expr length /" dying with a syntax
	# error, we prepend X to all length determinations.  Since all
	# we really care about are relative lengths, this has no
	# effect -- a constant offset of 1 doesn't really matter....

	dir=${1}
	longest=""
	length=0
	result=1
	for i in `cat /etc/fstab /etc/mtab | awk '{print $2}'`
	do
		if [ 0 != `expr "${dir}" : "${i}.*"` \
			-a \( `expr length "X$i" - 1` -gt $length \) ]
		then
			longest="${i}"
			length=`expr length "X$i" - 1`
		fi
	done

	# The bit with head is because we will (probably) get multiple
	# entries returned.  Just to avoid confusing expr (and myself),
	# it's easist to throw away any duplicates.

	if [ "$longest" != "" ]
	then
		# That's [<spc><tab>]
		source=`cat /etc/fstab /etc/mtab \
			| grep -e '[ 	]'$longest'[ 	]' \
			| head -1 \
			| awk '{print $1}'`
		if [ "`expr "${source}" : '..*\(:\)'`" = ":" ]
		then
			# Return true -- it's not a local mount
			result=0
		fi
	fi

	return $result
}

#
# error() prints an error message to stderr
#

error()
{
	echo "$Myname: $*" 1>&2
}

#
# usage() prints a usage message (usage is in the variable Usage)
#

usage()
{
	echo "usage: $Myname $Usage" 1>&2
	exit 2
}


#
# Signal cleanup.  Print message to user and to log
#

sig_abort()
{
	log "Aborted by signal"
}

#
# Record message in installation log
#
log()
{
	echo "$Myname: $*" >>${LOG}
}

#
# Final command wrap up.  Record errors and completion in log.
# Remove temporary files
#

wrapup()
{
	trap '' 1 2 3 15

	if [ -f ${TMP_ERR} ]
	then
		cat ${TMP_ERR} >>${LOG}
	fi

	log "Completed with status $1 `date`"
	rm -f ${TMP_ERR} ${TMP_EXP}
	if [ "$1" != "0" ]
	then
		error "Update failed due to error"
	fi
	exit $1
}

#
# /usr/bin/dirname is part of the SysV package, and so it's existence can
# not be counted on
#
dirname()
{
	expr ${1-.}'/' : '\(/\)[^/]*/$' \| ${1-.}'/' : '\(.*[^/]\)//*[^/][^/]*//*$' \| .
}

#
# Perform main function
#

main ${1+"$@"}
exit 0
