#!/bin/sh
# Copyright Precedence Technologies Ltd 2018

usage()
{
	cat << EOF
Syntax: updateos [-hnpv] [-m mode] [-k kernel] [ -d path ] [<path to sets>]

	-d = path to set lists for preen (otherwise /etc/mtree)
	-h = this help
	-k = override detected kernel
	-m = adjust installed sets to match mode (requires path
	     if missing sets need adding)
	-n = debug (don't make any changes)
	-p = preen (if no path given, check what is on disk matches
	     set lists, i.e have not been modified. If path given,
	     compare installed with new sets)
	-v = verbose (list each file checked, extracted or deleted)

mode is one of:
	minimal = minimum to run, no compilers, man pages, etc.
	nox = full base, compilers, etc. but no X11
EOF
	exit
}

checkmatched()
{
        local m i
        m=$1
        shift
        [ -z "$1" ] && return 1
        for i in $*
        do
                [ "$m" = "$i" ] && return 0
        done
        return 1
}

#./usr/bin type=dir uname=root gname=wheel mode=0755 optional
#./usr/bin/addftinfo type=file uname=root gname=wheel mode=0555 size=29021 sha256=bf62123bdd20b9be790fff5520d7dfef6d6fbdc61d1bc910df2f0cf836fb300c

removeset()
{
	local setname IFS line p t f o dl d
	setname=$1
	if [ ! -s "/etc/mtree/set.$setname" ]; then
		echo "Set not installed"
		exit 1
	fi
	IFS='
'
	dl=""
	while read line
	do
		IFS=' 	
'
		set $line
		file="$1"
		[ "$file" = "." ] && continue
		file="${file#.}"
		# Delete set list at end
		[ "$file" = /etc/mtree/set.$setname ] && continue
		shift
		t=""
		o=""
		for p in $*
		do
			case "$p" in
			type=*)
				t=${p#*=}
				;;
			optional)
				o=opt
				;;
			esac
		done
		
		if [ "$t" = file -a -f $file ]; then
			[ -n "$verb" ] && echo "($set) Delete file $file"
			[ -z "$debug" ] && rm $file
		elif [ "$t" = link -a -e $file ]; then
			[ -n "$verb" ] && echo "($set) Delete link $file"
			[ -z "$debug" ] && rm $file
		elif [ "$t" = dir -a -n "$opt" ]; then
			dl="${dl}${dl:+ }$file"
		fi
	done < /etc/mtree/set.$setname
	for d in $dl
	do
		f=`ls $d`
		if [ -z "$f" ]; then
			# empty dir
			[ -n "$verb" ] && echo "($set) Delete dir $d"
			[ -z "$debug" ] && rmdir $d
		fi
	done
	[ -n "$verb" ] && echo "($set) Delete file /etc/mtree/set.$setname"
	[ -z "$debug" ] && rm /etc/mtree/set.$setname
}

preenset()
{
	local setname root TMP sf mtree
	setname=$1
	mtree=$2
	root="$3"
	
	if [ -n "$root" ]; then
		if [ ! -s "$root/$setname.tgz" ]; then
			echo "Set $setname not found in $root"
			exit 1
		fi
		TMP=/tmp/updateos.$$
		[ ! -d $TMP ] && mkdir $TMP
		tar -xpzf $root/$setname.tgz -C $TMP
		sf=$TMP/etc/mtree/set.$setname
		if [ ! -s "$sf" ]; then
			echo "Could not find file list in $root/$setname.tgz"
			rm -r $TMP
			exit 1
		fi
		echo "Checking $setname against sets at $root"
	elif [ ! -s "$mtree/set.$setname" ]; then
		echo "Set $setname not installed"
		exit 1
	else
		sf=$mtree/set.$setname
		echo "Checking $setname against installed list:"
	fi
	egrep -v "^\./(usr/share/man/cat|var/run|usr/X11R7/man/cat|mnt)" $sf | \
		mtree -p / -e -k cksum
	ret=$?
	[ -n "$root" ] && rm -r $TMP
	return $ret
}

installed=""
for i in /etc/mtree/set.*
do
	name=${i##*/set.}
	installed="${installed}${installed:+ }$name"
done

echo "Installed sets: $installed"
kernel=`sysctl -n kern.configname`
kernel=${kernel%.*}
echo -n "Current kernel: $kernel"
case "$kernel" in
XEN3_DOMU|GENERIC)
	echo
	;;
*)
	echo " (non-standard)"
	;;
esac

mode=""
debug=""
preen=""
verb=""
vflag=""
mtree="/etc/mtree"
while getopts :d:hk:m:npv OPTION
do
	case $OPTION in
	d)
		mtree=$OPTARG
		if [ ! -d "$mtree" ]; then
			echo "$mtree does not exist"
			exit 1
		fi
		mlist=""
		for i in $mtree/set.*
		do
			name=${i##*/set.}
			mlist="${mlist}${mlist:+ }$name"
		done
		if [ "$mlist" != "$installed" ]; then
			echo "*WARNING* Set lists at $mtree do not match /etc/mtree"
			echo "$mlist"
		fi
		;;
	k)
		kernel=$OPTARG
		;;
	m)
		mode=$OPTARG
		;;
	n)
		debug=y
		;;
	p)
		preen=y
		;;
	v)
		verb=y
		vflag=v
		;;
	h|?)
		usage
		exit
		;;
	esac
done
shift `expr $OPTIND - 1`
root=""

case "$mode" in
minimal)
	sets="base etc modules"
	;;
nox|nox11)
	sets="base comp etc games man misc modules tests text"
	;;
*)
	mode=""
	sets="$installed"
	;;
esac
if [ -n "$1" ]; then
	if [ -f "$1/base.tgz" ]; then
		root="$1"
	elif [ -f "$1/amd64/binary/sets/base.tgz" ]; then
		root="$1/amd64/binary/sets"
	else
		echo "sets cannot be found at $1"
		exit 1
	fi
	if [ ! -f $root/kern-${kernel}.tgz ]; then
		echo "Kernel not found at $root/kern-${kernel}.tgz"
		exit 1
	fi
fi


if [ -n "$mode" ]; then
	# Check installed sets match required sets
	for s in $installed
	do
		# Never touch etc
		[ "$s" = "etc" ] && continue
		if ! checkmatched $s $sets; then
			echo "* Remove $s"
			removeset $s
		fi
	done
fi

if [ -n "$preen" ]; then
	modsets=""
	for s in $sets
	do
		case "$s" in
		etc|xetc)
			;;
		*)
			preenset $s $mtree $root
			[ "$?" = 2 ] && modsets="$modset${modsets:+ }$s"
			;;
		esac
	done
	if [ -n "$modsets" ]; then
		echo "The following sets have modified files: $modsets"
	fi
fi

if [ -z "$root" -o -n "$preen" ]; then
	# No path to sets, so no refresh
	exit
fi

cd /
postopts=""
for i in $sets kern-${kernel}
do
	[ "$i" = etc ] && continue
	if [ "$i" = xetc ]; then
		postopts="-s $root/xetc.tgz"
		continue
	fi
	echo "Extracting $i"
	if [ -z "$debug" ]; then
		tar -xpz${vflag}f $root/$i.tgz
		if [ "$?" != 0 -a $i != base ]; then
			echo "Exiting..."
			exit 1
		fi
	fi
done
if [ -n "$debug" ]; then
	echo "Running: postinstall -s $root/etc.tgz $postopts fix"
else
	postinstall -s $root/etc.tgz $postopts fix
fi
