#!/bin/sh
#
# pkgadd, pkgrm, pkgclean, pkginfo 2.9 - Optional packages.
#							Author: Kees J. Bot

# Which of three functions to perform?
action=`basename $0`

# Only make or delete links, i.e. do or don't remove the /opt/name tree?
full=n

# Clean a package leaving only binaries?
clobber=n

permission()
# Should be run as user 'bin' group 'operator' with permission to write
# the packages file.
{
	case "`id`" in
	'uid=2(bin) gid=0(operator)'*)
		: fine
		;;
	*)	echo "Package installation or removal should be done by bin" >&2
		exit 1
	esac

	(>>/usr/lib/packages) 2>/dev/null || {
		echo "$action: /usr/lib/packages cannot be written to." >&2
		exit 1
	}
}

root()
# Execute a command as root.
{
	su root -c 'exec "$@"' "$@"
}

member()
# Successful if $1 is a member of the list ${2..}.
{
	local look="$1"
	shift

	test $# = 0 && return 1
	test "$look" = "$1" && return 0
	shift
	member "$look" "$@"
}

get_header()
# Get a header $1 from an info file $2.
{
	sed -n -e "/^\$/q;s/^$1:[  ]*\\(.*\\)/\\1/p" "$2"
}

check_present()
# Check if a list of packages is present.
{
	local IFS="$IFS,"
	local new="$1"
	local dep="$2"
	local name
	local notthere
	local plural=n

	for name in $dep
	do
		if grep "^$name/" /usr/lib/packages >/dev/null
		then
			: fine
		else
			test -n "$notthere" && plural=y
			notthere="$notthere $name"
		fi
	done

	if [ -n "$notthere" -a $plural = n ]
	then
		cat >&2 <<DEPEND
$action: This package is missing:$notthere
	$new depends on it.  Please install it first and then finish
	installation of $new with 'pkgadd $new'.
DEPEND
		exit 1
	fi

	if [ -n "$notthere" ]
	then
		cat >&2 <<DEPEND
$action: These packages are missing:$notthere
	$new depends on them.  Please install them first and then finish
	installation of $new with 'pkgadd $new'.
DEPEND
		exit 1
	fi
}

check_depend()
# Check if a package is needed by another package making removal impossible.
{
	local ifs="$IFS"
	local IFS=/
	local old="$1"
	local name dep info
	local need

	while read name dep info
	do
		IFS="$ifs"
		if [ "$name" != "$old" ] && member "$old" $dep
		then
			need="$need $name"
		fi
		IFS=/
	done < /usr/lib/packages

	if [ -n "$need" ]
	then
		cat >&2 <<NEEDED
$action: $old can't be removed, it is required by:$need
NEEDED
		exit 1
	fi
}

readlink()
# Show where a symlink points to.
{
	expr "$(ls -ld "$1")" : 'l.* -> \(.*\)'
}

pkginfo()
# Give a one line description of the packages installed.
{
	local name dep info
	local IFS=/

	if [ $# = 0 ]
	then
		# Show one line for all installed packages.
		while read name dep info
		do
			if [ -f "/opt/$name/etc/info" ]
			then	
				echo "$name - $info"
			fi
		done < /usr/lib/packages
	else
		name="$1"
		shift
		if [ ! -f "/opt/$name/etc/info" ]
		then
			echo "$action: Package $name is not present" >&2
			exit 1
		fi

		cat "/opt/$name/etc/info"

		grep "^$name/" /usr/lib/packages >/dev/null || \
			echo "(Note: Package $name is not installed)"
		if [ $# != 0 ]
		then
			echo +----
			pkginfo "$@"
		fi
	fi
}

pkgadd()
# Add a package to the system.
{
	local name file optfile usrfile dep

	if [ $# = 0 -a $full = y -a -d "$distpkg" ]
	then
		name="$(basename "$distpkg")"
	else
		test $# = 1 || usage
		name="$1"
	fi

	permission

	# Unpack the package in distribution form for a full installation.
	if [ $full = y ]
	then
		if [ -d "/opt/$name" ]
		then
			cat >&2 <<PRESENT
$action: $name is already present.  Either remove it, or finish its
installation with $action $name.
PRESENT
			exit 1
		fi

		# Check a distributed package tree in advance.
		if [ -d "$distpkg" ]
		then
			test /"$distpkg" : ".*/$name/*" \
					|| distpkg="$distpkg/$name"

			for file in "$distpkg/etc/info" "$distpkg/etc/symlinks"
			do
				if [ ! -f "$file" ]
				then
		echo "$action: $distpkg is not a package, $file is missing" >&2
					exit 1
				fi
			done
		fi

		echo "Unpacking $name in /opt."

		if [ x"$distpkg" = "x-" ]
		then
			if [ -t 0 ]
			then
				echo "$action: Input is not a package." >&2
				exit 1
			fi
			gzcat | \
				su root -c "cd /opt && exec tar xfp - '$name'"
		elif [ -b "$distpkg" -o -c "$distpkg" ]
		then
			vol -1 "$distpkg" | gzcat | \
				su root -c "cd /opt && exec tar xfp - '$name'"
		elif [ -d "$distpkg" ]
		then
			root cpdir "$distpkg" "/opt/$name"
		elif [ x"$distpkg" : x'.*\.tar$' \
			-o x"$distpkg" : x'.*\.TAR$' ]
		then
			< "$distpkg" \
				su root -c "cd /opt && exec tar xfp - '$name'"
		else
			gzcat <"$distpkg" | \
				su root -c "cd /opt && exec tar xfp - '$name'"
		fi || exit

		if [ ! -d "/opt/$name" ]
		then
			echo "\
$action: $name: not extracted, is '$name' correct?" >&2
		    	exit 1
		fi

		exec </dev/null
	fi

	# The package must be there in /opt now.
	if [ ! -d "/opt/$name" ]
	then
		echo "$action: $name: no such package" >&2; exit 1
	fi

	# There must be info and symlinks files.
	for file in "/opt/$name/etc/info" "/opt/$name/etc/symlinks"
	do
		if [ ! -f "$file" ]
		then
			echo "$action: $name is bad, $file is missing" >&2
			exit 1
		fi
	done

	# Get and check the list of dependencies.
	dep="$(get_header 'Requires' "/opt/$name/etc/info")"

	check_present "$name" "$dep"

	# Make all the necessary symlinks to make the package work.
	echo "Making symlinks."
	while read optfile usrfile
	do
		if [ "$optfile" != "$(readlink "$usrfile" 2>/dev/null)" ]
		then
			root ln -sfm "$optfile" "$usrfile" || exit
			echo "ln -s $optfile $usrfile"
		fi
	done < "/opt/$name/etc/symlinks"

	if [ -d "/opt/$name/src" ]
	then
		echo "Running make in /opt/$name/build."
		if clone "/opt/$name/src" "/opt/$name/build" && \
			cd "/opt/$name/build" && make install
		then
			: happy
		else
			echo "\
Please correct the problems and rerun '$action $name'" >&2
			exit 1
		fi
	fi

	cd / || exit

	# An install script may tie up a few loose ends, it should not be
	# needed.
	if [ -f "/opt/$name/etc/install" ]
	then
		echo "Running /opt/$name/etc/install."
		su root "/opt/$name/etc/install" || exit
	fi

	# An rc file may start a daemon or two.
	if [ -f "/opt/$name/etc/rc" ]
	then
		echo "Running /opt/$name/etc/rc start."
		su root "/opt/$name/etc/rc" start || exit
	fi

	# Add a line to the list of installed packages.
	{
		test -f /usr/lib/packages && \
			grep -v "^$name/" /usr/lib/packages
		title="$(get_header 'Title' "/opt/$name/etc/info")"
		echo "$name/$(IFS="$IFS,"; echo $dep)/$title"
	} | sort -o /tmp/pkgs$$
	cmp -s /tmp/pkgs$$ /usr/lib/packages || cp /tmp/pkgs$$ /usr/lib/packages
	rm /tmp/pkgs$$

	echo +----
	cat "/opt/$name/etc/info"
	echo +----
	echo "Package $name installed."
	return 0
}

pkgrm()
# Remove a package.
{
	local name optfile usrfile

	test $# = 1 || usage
	name="$1"

	permission

	# Find the package.
	if [ ! -d "/opt/$name" ]
	then
		echo "$action: $name: no such package" >&2; exit 1
	fi

	# You can't remove a package if some other package depends on it.
	check_depend "$name"

	# Kill any daemons.
	if [ -f "/opt/$name/etc/rc" ]
	then
		echo "Running /opt/$name/etc/rc stop."
		su root "/opt/$name/etc/rc" stop || exit
	fi

	# Run make uninstall and remove the build tree (if any.)
	if [ $full = n -a -d "/opt/$name/src" ]
	then
		cd "/opt/$name/src" || exit
		echo "Running make uninstall and removing /opt/$name/build."
		make uninstall || exit
		cd /
		if [ -d "/opt/$name/build" ]
		then
			rm -r "/opt/$name/build" </dev/null || exit
		fi
	fi

	# Run the uninstall script that should do nothing.
	if [ -f "/opt/$name/etc/uninstall" ]
	then
		echo "Running /opt/$name/etc/uninstall."
		su root "/opt/$name/etc/uninstall" || exit
	fi

	# Remove the symlinks that desecrate /usr.
	echo "Removing symlinks."
	while read optfile usrfile
	do
		rm "$usrfile" && echo "rm $usrfile -> $optfile"
	done < "/opt/$name/etc/symlinks"

	# Remove the info line.
	grep -v "^$name/" /usr/lib/packages | sort -o /usr/lib/packages

	# For a full removal, delete the tree too.
	if [ $full = y ]
	then
		echo "Removing /opt/$name."
		cd /
		root rm -r "/opt/$name" </dev/null || exit

		echo "Package $name removed."
	fi
	return 0
}

pkgclean()
# Clean a package, optionally leaving a binary package.
{
	local name

	test $# = 1 || usage
	name="$1"

	# Exists?
	if [ ! -d "/opt/$name" ]
	then
		echo "$action: no package $name" >&2
		exit 1
	fi

	# Installed?
	if grep "^$name/" /usr/lib/packages >/dev/null
	then
		: ok
	else
		echo "$action: package $name is not installed" >&2
		exit 1
	fi

	# Binary packages cannot be cleaned.
	if [ ! -d "/opt/$name/src" ]
	then
		echo "Package $name is a binary package, so nothing to clean."
		exit
	fi

	# Run make clean and remove the build tree if any.
	cd "/opt/$name/src" || exit
	echo "Running make clean and removing /opt/$name/build."
	make clean || exit
	cd /
	if [ -d "/opt/$name/build" ]
	then
		rm -r "/opt/$name/build" </dev/null || exit
	fi

	if [ $clobber = y ]
	then
		# Leave a binary package.
		echo "Removing the source tree /opt/$name/src."
		rm -r "/opt/$name/src" </dev/null || exit
		echo "Package $name is now a binary package."
	else
		echo "Package $name is clean."
	fi
}

usage()
{
	case $action in
	pkgadd)		echo "Usage: pkgadd [-x distpkg] <name>"	;;
	pkgrm)		echo "Usage: pkgrm [-f] <name>"			;;
	pkgclean)	echo "Usage: pkgclean [-b] <name>"		;;
	pkginfo)	echo "Usage: pkginfo [name ...]"			;;
	*)		echo "$0???  pkgadd, rm, clean, or info?"	;;
	esac >&2
	exit 1
}

case $action in
pkgadd)		opts='x:'	;;
pkgrm)		opts='f'	;;
pkgclean)	opts='b'	;;
pkginfo)	opts=''		;;
*)		usage		;;
esac

while getopts "$opts" opt
do
	case $opt in
	x)	full=y; distpkg=$OPTARG
		;;
	f)	full=y
		;;
	b)	clobber=y
		;;
	?)	usage
	esac
done
shift `expr $OPTIND - 1`

$action "$@"
