#!/bin/sh
#
# $Id: pkg,v 1.2 1993/02/19 13:00:48 mike Exp $

# Save the name of the script for later. That way I don't have to hardcode
# the name all over before I've decided what it will finally be :-)
progname=`basename $0`

# If the default target directory isn't set it defaults to the current
# directory.
DEF_TARGET=.

# The default path to search is somewhat arbitrary...
PKGPATH=.:/packages:/usr/packages:/usr/local/packages:/var/packages

# A list of possible file system types (used when trying to mount disks).
FSTYPES="msdos ext2 xiafs minix ext"


# If a defaults file exists parse it into shell syntax and source it. We
# don't keep defaults files in shell syntax to avoid tempting people to
# source them directly. This could be funny...
if [ -r /etc/default/package ]; then
	awk '
/^#/		{ next; }
/^[ \t]*$/	{ next; }
		{ printf "%s=%s ; export %s\n", $1, $2, $1 }
	' /etc/default/package > /tmp/pkg$$ && . /tmp/pkg$$
	rm -f /tmp/pkg$$
fi

target="$DEF_TARGET"
if [ "$target" = "/" ]; then
	target=
fi

# Compression commands. You may be using compress, freeze, gzip...
# These commands should work in pipelines...
#COMP=compress
COMP=gzip
#UNCOMP=uncompress
UNCOMP=gunzip
ZCAT=zcat


# If this is a traditional old Bourne shell we can probably use backslash
# escapes regardless. If this is Bash we may need the -e option to the
# built in echo. This seems to make Bash incompatible? Is this POSIX
# behaviour? Is it a compile time configuration option in Bash? I dunno...
if [ -n "$BASH_VERSION" ]; then
	use_escapes="-e"
else
	use_escapes=""
fi


function usage()
{
	cat << !!MIKE_WANTS_CHOCCY!!
Options:
	-n
		Does not actually make changes to the target directory
		during a rebuild.

	-a|--add <package>
		Adds the package in the specified directory to the list
		of packages which are installed in the current target
		directory.

	--doc|--readme <package>
		Browse the documentation for the given package.

	-i|--info <regular expression>
		Give information about any files installed in the current
		target directory which match the given regular expression.

	--install <archive> | <device>
		Extract from the given archive and add the packages found
		to the current target directory. If the given name is a
		device then it is mounted and all archives found on it are
		extracted into the current directory.

	-l|--list
		List the packages intalled in the current target directory.

	-r|--rebuild
		Rebuild the current target directory by merging the map
		files of the installed packages and applying the resulting
		map instructions.

	-r|--remove <package>
		Remove the package in the specified directory from the
		list of packages which are installed in the current target
		directory.

	-t|--target <directory>
		Sets the current directory for installation.
!!MIKE_WANTS_CHOCCY!!
}


function read_docs()
{
	HERE=`pwd`
	TOPDIR=$1/doc

	if [ ! -d $1/doc ]; then
		echo "No documentation found in $1"
		return
	fi

	cd $1/doc

	echo
	echo "Available documents:"
	echo
	ls -C
	echo

	doc=
	while [ "$doc" != "quit" ]
	do
		echo -n "Enter document name (type quit when done): "
		read doc
		if [ -z "$doc" ]; then
			echo
			echo "Available documents:"
			echo
			ls -C
			echo
		elif [ -d "$doc" ]; then
			case "$doc" in
				..*|./..*)
					if [ "`pwd`" = $TOPDIR ]; then
						echo
						echo "Already in the top directory"
						echo
						doc=
					else
						cd $doc
						echo
						echo "Available documents:"
						echo
						ls -C
						echo
					fi
					;;
				*)
					cd $doc
					echo
					echo "Document directory $doc contains the following:"
					echo
					ls -C
					echo
					;;
			esac
		elif [ -f "$doc" ]; then
			echo
			${PAGER:-less} $doc
			echo
		elif [ "$doc" != "quit" ]; then
			echo
			echo "No such document or directory: $doc"
			echo
		fi
	done

	cd $HERE
}


function location()
{
	# If we have been given a path list we'll use it.
	if [ "$2" = "" ]; then
		path=$PKGPATH
	else
		path=$2
	fi

	echo $path | tr ':' '\n' | while read p
	do
		if [ -n "$p" -a "$p" != "." ]; then
			it=`ls -1dr $p/$1 $p/$1-* 2> /dev/null | head -1`
		else
			it=`ls -1dr $1 $1-* 2> /dev/null | head -1`
		fi
		if [ -n "$it" ]; then
			echo $it
			exit
		fi
#		if [ -e $p/$1 ]; then
#			if [ "$p" != "." ]; then
#				echo $p/$1
#			else
#				echo $1
#			fi
#			exit
#		fi
	done
}


function gen_list()
{
	if [ ! -r $1/PKG/map -a -z "$quiet" ]; then
		echo $use_escapes "(no map) \c" 1>&2
		return
	fi

	awk '
BEGIN		{ def_ver = "0" }
/^#/		{ done=1 }
/^[ \t]*$/	{ done=1 }
$1 == "VERSION"	{
			def_ver = $2
			done = 1
		}
done == 0	{
			if ($2 ~ /[*?]/) {
				op = $1
				dest = $3
				if (NF == 4)
					ver = $4
				else
					ver = def_ver
				cmd = sprintf("ls -1 %s/%s 2> /dev/null; exit 0", basedir, $2)
				while (cmd | getline) {
					printf "%s %s %s/%s %s\n", op, $0, dest, substr($0, 1+match($0, "/[^/]*$")), ver
				}
			} else {
				if (NF == 4)
					ver = $4
				else
					ver = def_ver
				printf "%s %s/%s %s %s\n", $1, basedir, $2, $3, ver
			}
		}
		{ done=0 }
	' basedir=$1 $1/PKG/map
}


function do_install()
{
	target=$1

	$ZCAT $target/.map | awk '
BEGIN		{
			last_path = ""
		}
$3 != last_path	{
			if (match($3, "^/.*") == 1)
				printf "%s %s %s\n", $1, $2, $3
			else
				printf "%s %s %s/%s\n", $1, $2, target, $3
			last_path = $3
		}
	' target=$target | /usr/lib/pkg/pipe-inst $no_action
}


function do_purge()
{
	dir=$1

	$ZCAT $dir/.map | awk '
BEGIN		{
			last_path = ""
		}
$3 == last_path	{
			print $2
			last_path = $3
		}
	' target=$dir | /usr/lib/pkg/pipe-rm $no_action
}


function mount_disk()
{
	for i in $FSTYPES
	do
		mount -t $i $1 $2 2> /dev/null
		if [ $? -eq 0 ]; then
			ok=yes
			break
		fi
	done
	if [ -z "$ok" ]; then
		echo "error: unable to mount disk." 1>&2
		return 1
	else
		return 0
	fi
}


function add_package()
{
	if [ -f $target/.installed ]; then
		curr_pkg=`egrep "/$1(-[-0123456789.]*)?$" $target/.installed`
	else
		curr_pkg=
	fi
	if [ -n "$curr_pkg" ]; then
		if [ -z "$quiet" ]; then
			echo $1 is already installed here from $curr_pkg.
		fi
	else
		locn=`location $1`
		[ -n "$locn" ] && case "$locn" in
			/*)
				echo $locn >> $target/.installed \
				&& [ -z "$quiet" ] && echo "Added $1 to $target."
				;;

			*)
				echo `pwd`/$locn >> $target/.installed \
				&& [ -z "$quiet" ] && echo "Added $1 to $target"
				;;
		esac
	fi
}


function remove_package()
{
	if [ -z "$quiet" ]; then
		echo "Remove from $target:"
		egrep "$1" $target/.installed
	fi
	mv $target/.installed $target/.installed.old
	egrep -v "/$1(-[-0123456789.]*)?$" $target/.installed.old > $target/.installed
	rm -f $target/.installed.old
}


function install_package()
{
	# Note what we have already.
	ls -1 > /tmp/pkgA$$

	# If we are given a block device we try and mount it, extract any
	# packages we find on it and then unmount it again. Otherwise we
	# assume we were just given a package name.
	if [ -b "$1" ]; then
		rm -f /tmp/pkgm$$
		mkdir /tmp/pkgm$$
		if mount_disk $1 /tmp/pkgm$$; then
			for i in /tmp/pkgm$$/*
			do
				[ -z "$quiet" ] && echo "Extracting "`basename $i`
				tar xfzpsS "$i"
			done

			umount /tmp/pkgm$$
			rmdir /tmp/pkgm$$
		else
			rm /tmp/pkgA$$
			rmdir /tmp/pkgm$$
			exit 1
		fi
	else
		[ -z "$quiet" ] && echo "Extracting $1"
		tar xfzpsS "$1"
	fi

	# Find what we have now.
	ls -1 > /tmp/pkgB$$

	# Add anything that was not here originally.
	comm -13 /tmp/pkgA$$ /tmp/pkgB$$ | while read i
	do
		add_package "$i"
	done
	rm -f /tmp/pkgA$$ /tmp/pkgB$$
}


# If we haven't any arguments just display the usage message.
if [ $# -eq 0 ]; then
	usage
	exit 1
fi

no_action=
quiet=
while [ $# -ne 0 ]
do
	case "$1" in
		-n)
			no_action=-n
			shift
			;;

		-q|--quiet|--quite)
			# Yeah, quite is a mispelling but we can guess
			# what they mean.
			quiet=yes
			shift
			;;

		-t|--target)
			if [ -z "$2" ]; then
				echo "$1 requires an argument"
				exit 1
			fi
			target=$2
			if [ "$target" = "/" ]; then
				target=
			fi
			shift; shift
			;;

		--install|--extract)
			if [ -z "$2" ]; then
				echo "$1 requires an argument" 1>&2
				exit 1
			fi
			shift

			done=
			while [ -n "$1" -a -z "$done" ]
			do
				case "$1" in
					-*)	done=yes
						;;
					*)
						install_package "$1"
						shift
						;;
				esac
			done
			;;
		-a|--add)
			if [ -z "$2" ]; then
				echo "$1 requires an argument" 1>&2
				exit 1
			fi
			shift
			done=
			while [ -n "$1" -a -z "$done" ]
			do
				case "$1" in
					-*)	done=yes
						;;
					*)	add_package "$1"
						shift
						;;
				esac
			done
			;;

		--purge)
			echo
			echo "This operation removes files from the actual package directories which"
			echo "have been superceeded by later versions and are no longer required on"
			echo "your system."
			echo
			ans=
			while [ "$ans" != "y" -a "$ans" != "Y" -a "$ans" != "n" -a "$ans" != "N" ]
			do
				echo $use_escapes "Are you sure you want to do this? (y/n): \c"
				read ans
			done
			if [ "$ans" = "y" -o "$ans" = "Y" ]; then
				do_purge $target
			fi
			shift
			;;
		--conf*)
			if [ -z "$2" ]; then
				echo "$1 requires an argument" 1>&2
				exit 1
			fi
			if [ -f $target/.installed ]; then
				locn=`grep "$2" $target/.installed`
			else
				locn=
			fi
			if [ -z "$locn" ]; then
				locn=`location $2`
				[ -n "$locn" ] && case "$locn" in
					/*)
						;;
					*)
						locn=`pwd`/$locn
						;;
				esac
			fi
			if [ -n "$locn" ]; then
				if [ -x $locn/PKG/configure ]; then
					(cd $locn ; PKG/configure)
				else
					echo "No configuration is necessary for $2"
				fi
			else
				echo "can't find package $2"
			fi
			shift; shift
			;;

		--doc*|--read*)
			if [ -z "$2" ]; then
				echo "$1 requires an argument" 1>&2
				exit 1
			fi
			if [ -f $target/.installed ]; then
				locn=`grep "$2" $target/.installed`
			else
				locn=
			fi
			if [ -z "$locn" ]; then
				locn=`location $2`
				[ -n "$locn" ] && case "$locn" in
					/*)
						;;
					*)
						locn=`pwd`/$locn
						;;
				esac
			fi
			if [ -n "$locn" ]; then
				read_docs $locn
			else
				echo "can't find package $2"
			fi
			shift; shift
			;;
		-d|--del*|--rm|--rem*)
			if [ -z "$2" ]; then
				echo "$2 requires an argument" 1>&2
				exit 1
			fi
			shift
			done=
			while [ -n "$1" -a -z "$done" ]
			do
				case "$1" in
					-*)	done=yes
						;;
					*)	remove_package "$1"
						shift
						;;
				esac
			done
			;;

		-l|--list|--ls)
			if [ ! -s $target/.installed ]; then
				if [ -z "$target" ]; then
					echo "Nothing installed in /."
				else
					echo "Nothing installed in $target."
				fi
			else
				if [ -z "$target" ]; then
					echo "Packages installed in /:"
				else
					echo "Packages installed in $target:"
				fi
				awk -F/ '{
					printf "\t%-20s\t(%s)\n", $NF, $0
				}' $target/.installed
			fi
			shift
			;;

		-i|--info|--what*|--where*)
			if [ -z "$2" ]; then
				echo "--info requires an argument" 1>&2
				exit 1
			fi
			$ZCAT $target/.map | egrep $2 | awk '
BEGIN		{
			done=0
			last_path=""
		}
$3 != last_path	{
			print $3
			if ($4 != "0")
				print "\tVersion", $4
			last_path = $3
			done=0
		}

done == 0 && $1 == "COPY"	{ print "\tcopy of", $2 }
done == 0 && $1 == "COPYNEW"	{ print "\tbased on", $2 }
done == 0 && $1 == "LINK"	{ print "\tlink to", $2 }
done == 0 && $1 == "SLINK"	{ print "\tsymlink to", $2 }

done == 0	{ done=1 }
			'
			shift; shift
			;;

		-r|--rebuild)
			if [ ! -f $target/.installed ]; then
				echo "No packages installed in $target."
				echo "Use --add to add a package."
				exit 1
			fi
			[ -z "$quiet" ] && echo $use_escapes "Merging package maps:\n\t\c"
			rm -f $target/.map.old
			[ -f $target/.map ] && mv $target/.map $target/.map.old
			while read i
			do
				[ -z "$quiet" ] && echo $use_escapes "`basename $i` \c" 1>&2
				gen_list $i
			done < $target/.installed | sort -b -r +2 | $COMP > $target/.map
			[ -z "$quiet" ] && echo

			[ -z "$quiet" ] && echo
			if [ -f $target/.map.old ]; then
				[ -z "$quiet" ] && echo "Removing obsolete files:"
				$ZCAT $target/.map | awk '!/^COPYNEW/ { print $3 }' | sort | uniq > /tmp/pkg$$
				$ZCAT $target/.map.old | awk '!/^COPYNEW/ { print $3 }' | sort | uniq | comm -13 /tmp/pkg$$ - | /usr/lib/pkg/pipe-rm $no_action $target
				rm -f /tmp/pkg$$
			fi

			[ -z "$quiet" ] && echo $use_escapes "\nRunning pre-install scripts:\n\t\c"
			while read i
			do
				[ -z "$quiet" ] && echo $use_escapes "`basename $i` \c" 1>&2
				if [ -r $i/PKG/pre-install ]; then
					(cd $i; sh ./PKG/pre-install $no_action $target < /dev/tty > /dev/tty 2>&1)
				fi
			done < $target/.installed
			[ -z "$quiet" ] && echo

			[ -z "$quiet" ] && echo $use_escapes "\nBuilding target structure..."
			do_install $target

			[ -z "$quiet" ] && echo $use_escapes "\nRunning post-install scripts:\n\t\c"
			while read i
			do
				[ -z "$quiet" ] && echo $use_escapes "`basename $i` \c" 1>&2
				if [ -r $i/PKG/post-install ]; then
					(cd $i; sh ./PKG/post-install $no_action $target < /dev/tty > /dev/tty 2>&1)
				fi
			done < $target/.installed
			[ -z "$quiet" ] && echo

			if [ -n "$no_action" -a -f $target/.map.old ]; then
				rm -f $target/.map
				mv $target/.map.old $target/.map
			else
				rm -f $target/.map.old
			fi

			if [ -z "$no_action" -a -x /usr/lib/pkg/prepare ]; then
				/usr/lib/pkg/prepare $target
			fi
			shift
			;;

		*)
			echo "${progname}: unrecognised option \`$1'"
			echo "run ${progname} with no arguments for help"
			exit 1
			;;
	esac
done

# If the .installed file is newer than the .map file (if any) then we will
# need to rebuild the structure at some stage.
if [ -z "$quiet" -a -f $target/.installed ]; then
	if [ ! -f $target/.map -o $target/.installed -nt $target/.map ]; then
		echo "The target structure may not represent currently installed packages."
		echo "Use --rebuild to rebuild the target structure."
	fi
fi
