:
#	@(#) serial.sh 25.4 92/10/22 
#
#	Copyright (C) 1986-1992 The Santa Cruz Operation, Inc.
#		All Rights Reserved.
#	The information in this file is provided for the exclusive use of
#	the licensees of The Santa Cruz Operation, Inc.  Such users have the
#	right to use, modify, and incorporate this code into other products
#	for purposes authorized by the license agreement provided they include
#	this notice and the associated copyright notice with any such product.
#	The information in this file is provided "AS IS" without warranty.
#
#	mkdev/serial - Serial Port Configuration
#

LANG=english_us.ascii
PATH=/etc:/bin:/usr/bin:/etc/conf/bin:/tcb/bin:

export LANG PATH

SERPERM="/usr/lib/mkdev/perms/SER"
INIT="/etc/inittab"
CF_INIT="/etc/conf/cf.d/inittab"
SIO_INIT="/etc/conf/init.d/sio"
TTY_TYPE="/etc/ttytype"
tmp="/tmp/ser$$"
LIST="/etc/conf/pack.d/sio/space.c"
TMP1="/tmp/ser2.$$"
SPD=/tmp/modspeed.$$
SPDLIST=/tmp/list.$$
System=/etc/conf/sdevice.d/sio
TMP2=/tmp/sdev.$$
TMP3=/tmp/siot.$$
TMP4=/tmp/space.$$
NODE=/etc/conf/node.d/sio
UTILITY_LIST="/usr/bin/dc"
MDEV=/etc/conf/cf.d/mdevice
BASE=7

# Define return values
: ${OK=0}  ${FAIL=1}  ${STOP=10}  ${HALT=11}

# FUNCTION DEFINITIONS
#########################

# Define traps for critical and non critical code.
set_trap()  {	
	trap 'echo "Interrupted! Exiting ..."; cleanup 1' 1 2 3 15
}
unset_trap()  {
	trap '' 1 2 3 15
}

cleanup()  {
	trap '' 1 2 3 15
	rm -f System $tmp* $TMP1 $SPDLIST $SPD $TMP2 $TMP3 $TMP4
	exit $1
}

# clear the screen if it is supported
clearscr() {
	# check if the terminal environment is set up
	[ "$TERM" ] && clear 2> /dev/null
}

asklink()  {
	getyn "
You must create a new kernel to effect the driver change you specified.
Do you wish to create a new kernel now?"  ||  {
		echo "
To create a new kernel execute /etc/conf/cf.d/link_unix. Then you
must reboot your system by executing  /etc/shutdown -i0  before the 
changes you have specified will be implemented.\n"
			return $FAIL 
		}
	return $OK
}

# re-link new kernel
klink() {
	cd /etc/conf/cf.d
	./link_unix || { 
		echo "\nError: Kernel link failed."
		cleanup $FAIL
	}
	return $OK
}

getyn() {
	while	echo "$* (y/n) \c" >&2
	do	read yn rest
		case $yn in
		[yY])	return $OK 				;;
		[nN])	return $FAIL				;;
		*)	echo "\nPlease answer y or n" >&2	;;
		esac
	done
}

# Prompt with mesg, return non-zero on q. Also allows setting -x and +x,
# shell escapes and the passing of default values
prompt() {
	mesg=$1
	while	echo "\n${mesg}or enter q to quit: \c" >&2
	do	read cmd
		case $cmd in
		+x|-x)	set $cmd					;;
		-v)	# Print the values of requested variables
			read x
			for var in $x
			do
				eval echo \$$var
			done
			;;
		Q|q)	return $FAIL					;;
		!*)	eval `expr "$cmd" : "!\(.*\)"`			;;
		"")	# If there is a second argument use it
			# as the default, else loop until 'cmd' is set
			[ "$2" ] && { 
				cmd=$2
				return $OK
			}
			: continue
			;;
		*)	return $OK					;;
		esac
	done
}

# Print an error message
error() {
	echo "\nError: $*" >&2
	return $FAIL
}

# perms list needed if link kit must be installed
permschk () {
	if [ -f /etc/perms/extmd ]; then
		PERM=/etc/perms/extmd
	else
		error "Cannot locate /etc/perms/extmd. This file 
is needed to verify Link Kit installation."
		cleanup $FAIL
	fi
}

# test to see if link kit is installed
linkchk()  {
	cd /
	until	fixperm -i -d LINK $PERM
	do	case $? in
		4)  echo "\nThe Link Kit is not installed." >&2	;;
		5)  echo "\nThe Link Kit is only partially installed." >&2  ;;
		*)  echo "\nError testing for Link Kit.  Exiting." >&2; cleanup $FAIL  ;;
		esac

		# Not fully installed. Do so here
		getyn "\nDo you wish to install it now?" || {
			# answered no
			echo "
The Link Kit must be installed to run this program.  Exiting ..."
			cleanup $OK
		}

		# answered yes, so install link kit
		echo "\nInvoking /etc/custom\n"
		/etc/custom -o -i LINK || {
			# custom exited unsuccessfully
			error "custom failed to install Link Kit successfully.  Please try again."
			cleanup $FAIL
		}
	done
}

#
# utility_chk ()
#
# Check that the needed utilities are installed, if they aren't then
# custom install them one by one
############################
utility_chk () {

	#
	# check that the perms file /etc/perms/ext exists
	# we need this for grabbing the utilities
	#
	if [ -f /etc/perms/ext ] 
	then
		PERM=/etc/perms/ext
	else
		error "/etc/perms/ext is missing. This file is needed to execute this program."
		cleanup $FAIL
	fi

	cd /
	#
	#Initialise the loop condition
	#
	files=""
	for i in $UTILITY_LIST
	do
		[ -x $i ] || files="${files} $i"
	done
	#
	# loop until all the files are present or we quit 
	#
	while [ "x${files}" != "x" ]
	do 
		while echo "
The following utilities are not present:\n\n ${files}

Do you wish to install them now (y/n/q) ? \c"
		do
			read X
			case $X in
			q|Q)	cleanup $OK ;;
			n|N)	echo "
$files must be installed to run this program. Exiting ..."
				cleanup $OK ;;
			y|Y)	break ;;
			*)	continue ;;
			esac
		done
		#
		# custom install them
		#
		for i in $files
		do
			pkg=`grep $i $PERM | awk ' { print $1 }'`
			[ x$pkg = x ] && {
				error "Could not find $i in $PERM."
				cleanup $FAIL
			}
			echo "\nInvoking /etc/custom for $i\n"
			/etc/custom -o -i $pkg -f $i || {
				error "custom failed to install $i successfully. Please try again."
				cleanup $FAIL
			}
		done
		# reset the loop condition
		files=""
		for i in $UTILITY_LIST
		do
			[ -x $i ] || files="${files} $i"
		done
	done
}
#
# update_node ()
#
# Update the node.d/sio file to match the current configuration so that
# on future relinks we don't lose the serial ports
############################
update_node () {

	major=`grep "^sio[ 	]" $MDEV | awk ' { print $6 } '`
	([ -c /dev/tty?[a-zA-Z] ] && (
		ls -l /dev/tty?[a-zA-Z] | awk -F' '$major, ' { print $2 } ' |
		awk ' { sub("/dev/", "", $5);
			if ( $5 != "" ) printf("sio\t%s\tc\t%d\n", $5, $1);
		}' >$NODE
	)) || rm -f $NODE
}

#
# get_entry ( baud_rate  file_name )
# prompt the user for an entry specified by 'baud_rate' and present in given
# file
############################
get_entry() {
	baud=$1
	file=$2
	max=`wc -l <${file} | awk '{ printf("%1d", $1) }'`
	if [ ${max} -gt 1 ]
	then
		grammar="entries support"
	else
		grammar="entry supports"
	fi
	echo "\nThe following /etc/gettydefs(F) ${grammar} ${baud} baud lines:\n"
	while :
	do	cat ${file}
		echo "\nChoose one of the above entries or q to quit: \c"
		read x
		case $x in
		[qQ])	return 0					;;
		*)	if [ $x -gt $max ] || [ $x -lt 1 ]
			then
				echo "\nInvalid entry, must be between 1 and $max. Please try again\n"
			else
				return $x
			fi						;;
		esac
	done
}

#
# read_entry ( entry_number  file )
# read the given entry from file. Echo it as output.
#################
read_entry() 
{
	num=$1
	list=$2

	set `awk '
	BEGIN	{ i = 0; }
		{if (++i == '$num') {
			printf("%s\n", $2);
		}}
	'<$list`
	if [ x$1 = x ]
	then
		echo ""
	else
		echo "$1"
	fi
}

#
# getbaud() - ask the user for the baud rate to be used for a modem line
#	      produces a list of available entries from gettydefs which the
#	      user can choose from
#	    - returns gettydefs entry in 'mspd'
######################
getbaud ()  {
	while  echo "\nPlease enter the baud rate to be used on the modem devices: \c"
	do
		read X
		awk ' /#.*B'${X}'[ 	]/ { print } ' < /etc/gettydefs > $SPD
		awk '
	BEGIN	{ found = 0 ; }
		{ sub("#", "", $1) ;
	  	printf("	%d.  %s\n", ++found, $1 ) }
	' <${SPD} > $SPDLIST
	
		
		if [ `wc -l <$SPDLIST` -eq 0 ]
		then
			echo "Sorry, ${X} baud is not supported in /etc/gettydefs"
		else
			eval 'get_entry $X $SPDLIST'
			entry=$?
			if [ $entry -eq 0 ]
			then
				:
			else
				# get the actual entry that we need from the 
				# $SPDLIST and put it in the global mspd
				mspd=`read_entry $entry $SPDLIST`
				break
			fi
		fi

	done
	return $OK
}

#
# gen_entry( entry number, file )
# ---------
# read the given 'file' until we find the right entry number.
# calculate the start and end addresses for i/o ports
#################################
gen_entry () {
	x=$1
	tmp=$2

	set `awk '
	BEGIN	{i = 0;}
		{if (++i == '$x') {
			# calculate the sioe
			sub("0x", "", $5);
			# print the start i/o address + num ports and size
			printf("%s %d %d %s %d",$5, $10, $9, $7, $8);
			}}
	' <$tmp`
	if [ x$1 = x ]
	then
		echo ""
	else
		vec=$5
		fileoff=$4
		base=`echo $1 | tr '[a-f]' '[A-F]'`
		sios=`dc <<!
16i
$base
p
q
!`
		if [ "$3" = "0" ]
		then
			offset=$BASE
		else
			offset=`expr $2 '*' $3`
		fi
		sioe=`expr $sios '+' $offset`
		hsioe=`dc <<!
16o
$sioe
p
q
!`
		hsioe=`echo $hsioe | tr '[A-F]' '[a-f]'`
		echo "sio	Y	$fileoff	7	0	$vec	$1	$hsioe	0	0"
	fi
}

#
# show_config ()
# 	Display the current sio configuration as found in $System.
#	Return $OK if there are boards configured (user can delete)
#	Return $FAIL if no boards are found (user can add)
#######################

show_config()	{
	nentries=`wc -l <$System | awk ' { printf("%1d", $1) }' `
	if [ $nentries -eq 0 ]
	then
		return $FAIL
	else
		# get index for serial board from System file
		# show start i/o address and end i/o address
		awk '{ printf("%d %s\n", $3, $7) ; }' <$System > $TMP2

		# now parse the temp file and generate a list of configured boards

		(
		n=0
		while [ $n -lt $nentries ]
		do
			read index baseio
			awk -F, '
			BEGIN { nth = -1 ; }
			/\/\* START SIO BOARDS \*\//	{
				reading_cards = 1;
			}
			/\/\* END SIO BOARDS \*\//	{
				reading_cards = 0;
			}
			{
				if (reading_cards) {
					if ( nth++ == '$index' ) {
					gsub("[ /\*]", "", $10);
					sub("..-", "", $10);
					sub("{", "", $1);
					printf("\t%2d. %s %d port card i/o address = %s, interrupt = COM%d\n", '$n'+1, $10, $3, "'$baseio'", $1+1 );
					}
				}
			}
			' <$LIST >>$TMP3
			n=`expr $n + 1`
		done
		) < $TMP2

		echo "The following cards are currently configured:\n"

		cat $TMP3

		return $OK
	fi
}


# install_board ()
# 	add a specified serial board to the current configuration
#########################
install_board()  {

	while	echo "\nYou would like to install a:

	1.  1 port card
	2.  2 port card
	3.  4 port card
	4.  5 port card
	5.  8 port card
	6. 16 port card

Enter your choice or q to quit: \c"
		read x
	do	case $x in
		1)	ports=1; break					;;
		2)	ports=2; break					;;
		3)	ports=4; break					;;
		4)	ports=5; break					;;
		5)	ports=8; break					;;
		6)	ports=16; break					;;
		q)	echo "\nInstallation aborted" >&2; cleanup $OK	;;
		*)	echo "\nEnter 1, 2, 3, 4, 5, 6 or q\n" >&2	;;
		esac
	done

	while	echo "\nThe card is configured as:

	1. COM1
	2. COM2
	3. COM3
	4. COM4
	
Enter your choice or q to quit: \c"
		read x
	do	case $x in
		1)	com=1; break				;;
		2)	com=2; break				;;
		3)	com=3; break				;;
		4)	com=4; break				;;
		q|Q)	echo "\nInstallation aborted" >&2; cleanup $OK 	;;
		*)	echo "\nEnter 1, 2, 3, 4, or q\n" >&2	;;
		esac
	done

	PORTS=$ports
	COMPORT=$com

	UNIT="{`expr $COMPORT - 1`"

awk -F, '
BEGIN	{ reading_cards = 0; found = 0; nth = -1; arch = "'$ARCH'-"; }
/\/\* START SIO BOARDS \*\//	{
		reading_cards = 1;
	}
/\/\* END SIO BOARDS \*\//	{
		reading_cards = 0;
	}
	{
		if (reading_cards) {
			if (match($10, arch) != 0) {
				sub(arch, "", $10);
				gsub("[ 	][ 	]*", "", $3);
				gsub(".sd.", "", $6);
				if (("'$UNIT'" == $1) && ("'$PORTS'" == $3)) {
					gsub("[ /\*]", "", $10);
					printf("        %3d. %-15s base address %s : %d %d %d %d\n", ++found, $10, $6, nth, $4, $8, $3);
				}
			}
			nth++;
		}
	}
' <$LIST >$TMP1

	# test to see if any of that kind exist
	num_cards=`wc -l <$TMP1 | awk '{ printf("%1d", $1) }'`
	if [ $num_cards -eq 0 ]
	then
		echo "\nSorry - no cards in that configuration are supported."
		cleanup $FAIL
	fi

	echo "\nWhich card do you have (the following are supported) ?\n"
	sed -e 's/\(..*\):..*/\1/' <$TMP1

	while echo "\nEnter your choice or q to quit: \c"
	do	read x
		case $x in
		q|Q)	echo "Installation aborted" >&2; rm -f $TMP1; exit 0	;;
		"")	: nothing ;;
		[0-9]*)	if [ $x -gt $num_cards ] || [ $x -lt 1 ]
			then
		    		echo "\nInvalid entry, please choose between 1 and $num_cards"
		    		continue
			else
		    		FIND=`gen_entry $x $TMP1`
		    		if [ "x$FIND" = x ]
		    		then
					continue
		    		else
					break
		    		fi 
			fi	;;
		*)	echo "\nInvalid entry, please choose between 1 and $num_cards"
			continue ;;
		esac
	done
	
	# check for duplication
	/etc/conf/bin/idinstall -gs sio > System || cleanup $FAIL
	set $FIND
	grep $3 System 1>/dev/null 2>/dev/null
	if [ "$?" -eq "0" ]
	then
		echo '\nThat card is already configured in the kernel.'
		cleanup $FAIL
	fi
	
	# Create init.d/sio file if it doesn't exist.
	# This contains information to rebuild inittab.
	[ -f $SIO_INIT ] || {
		umask 133
		> $SIO_INIT
		chgrp sys /$SIO_INIT
		umask 022
	}
	
	getbaud
	
	fixperm -c -d SER$com $SERPERM 2> /dev/null || {
		echo "\nError: cannot create devices" >&2
		cleanup $FAIL 
	}
	
	# Fixperm makes 8 ports at a time, so remove unneeded ones
	case $ports in
 	1)	rm -f /dev/tty${com}[b-pB-P]	;;
 	2)	rm -f /dev/tty${com}[c-pC-P]	;;
 	4)	rm -f /dev/tty${com}[e-pE-P]	;;
 	5)	rm -f /dev/tty${com}[f-pF-P]	;;
 	8)	rm -f /dev/tty${com}[i-pI-P]	;;
	16)	fixperm -c -d SER${com}16 $SERPERM 2> /dev/null || {
			echo "\nError: cannot create devices" >&2
			cleanup $FAIL
		}				;;
	esac

	cp $SIO_INIT $tmp.sio
	cp $TTY_TYPE $tmp.type

	echo "\nModifying system files ...\c"

	# update the node.d file
	update_node

	# modify the sdevice file
	echo $FIND | sed -e 's/ /	/g' >> System
	/etc/conf/bin/idinstall -us sio || cleanup $FAIL

	cd /dev
	for x in tty${com}?
	do	
		case $x in
		*[A-Z])	spd=$mspd	type=dialup	modn="$modn $x"		
			proc="/etc/getty -t60 $x $spd"				;;
		*[a-z])	spd=m
			[ $mspd = 19200 ] && spd=n 
			type=unknown	stdn="$stdn $x"		
			proc="/etc/getty $x $spd"				;;
		*[0-4]) continue						;;
		*)	echo "\nNon-standard device name in /dev" >&2; continue	;;
		esac
	
		# Remove any previous entry for tty and add entry to init.d directory 
		grep -v $x $tmp.sio  2> /dev/null 1> $tmp.sio1
		mv $tmp.sio1 $tmp.sio
		id=Se`expr "$x" : "tty\(..\)"`
		echo "$id:23:off:$proc" >> $tmp.sio
	
		# Add entry to ttytype
		grep  $x $tmp.type 2> /dev/null 1> /dev/null || {
			echo "$type\t$x" >> $tmp.type
		}
	done
	cd /

	unset_trap
	mv $tmp.sio $SIO_INIT
	set_trap

	# Create new inittab
	idmkinit || {
		echo "\nError: Idmkinit failed." >&2
		cleanup $FAIL
	}
	chmod 660 $CF_INIT

	unset_trap
	mv $CF_INIT $INIT
	mv $tmp.type $TTY_TYPE
	set_trap

	if ttys_update 
	then
		echo "\nSystem files successfully updated."
	else
		echo "\nError: ttys_update failed. 
You must update the security database /etc/auth/system/ttys 
with the following devices." >&2 
	fi

	telinit q
	echo "
The following standard serial device(s) have been installed:
	$stdn

The following device(s) access the same physical port as
their lower-case counterpart, but have modem control properties:
	$modn
"
	asklink && klink
}

# remove_board ()
# Remove one of the already configured boards from the kernel
########################
remove_board() {
	nentries=$1
	while echo "
Which board do you wish to remove ( 1 - ${nentries} or q to quit) ? \c"
	do
		read	n
		case $n in
		[0-9]|[0-9][0-9])
			if [ $n -lt 1 ] || [ $n -gt ${nentries} ]
			then
				echo "\nInvalid response, please try again"
				continue
			else
				break
			fi	;;
		"")	continue	;;
		q|Q)	echo "\nRemoval aborted" >&2; cleanup $OK	;;
		*)	echo "\nInvalid response, please try again"
			continue	;;
		esac
	done

	#
	# now got the board number in n so we can remove the entry from 
	# System
	# Get the unit number (COMx) so that we can remove the appropriate tty
	# devices

	set `awk ' FNR == '$n' {
		sub("COM", "", $12) ;
		printf("%d\n", $12+0);
	}' <$TMP3 `

	com=$1
	# remove /dev entries
	rm -f /dev/tty${com}[a-zA-Z]

	# update node.d file
	update_node

	# remove inittab entries
	[ -f ${SIO_INIT} ] && ( 
		grep -v Se${com} ${SIO_INIT} > $tmp.sio
		cp $tmp.sio ${SIO_INIT}
	)

	awk ' FNR != '$n' { print ; } '<$System >${System}.tmp
	cp ${System}.tmp ${System} || cleanup $FAIL

	asklink  && klink
	return $OK
}

# choose_action ()
# Prompt the user to choose between removing a board, adding a new board or
# quitting from the script
#########################
choose_action()	{
	while  echo "
	i)nstall a new serial board
	r)emove an existing board
	q)uit

	Please enter your choice (i/r/q) > \c"
	do
		read choice
		case $choice in
		i|I)	install_board
			return $OK		;;
		r|R)	remove_board $nentries
			return $OK		;;
		q|Q)	cleanup $OK		;;
		*)	echo "\nInvalid response, only 'r','i', 'q' allowed\n"
			continue		;;
		"")	continue		;;
		esac
	done
}

# main ()
#########################

set_trap
cd /
if cat /dev/mcapos 1>/dev/null 2>/dev/null
then ARCH=MC
else ARCH=AT
fi

clearscr
echo "\n\n\nSerial Board Initialization \n"

permschk
linkchk
utility_chk

(show_config && choose_action) || install_board

cleanup $OK
