#!/bin/sh
###############################################################################
#
#  Package Frontend.
#
#  By: Sulev-Madis Silber <ketas@si.pri.ee>
#
#
###############################################################################

# Configuration file

pkgfe_path="`realpath \"$0\"`"
pkgfe_prefix="${pkgfe_path%/*/*}"
pkgfe_config_dir="$pkgfe_prefix/etc"

config_file="$pkgfe_config_dir/pkgfe.conf"


# Paths

ports_path=${PORTSDIR:-/usr/ports}

pkgdb_path=${PKG_DBDIR:-/var/db/pkg}

temp_dir=${TMPDIR:-/tmp}


# Miscellaneous

pager_program=${PAGER:-less}


###############################################################################

#
# Returns program name without path.
#
prog()
{
	basename "$0"
}


#
# Returns "s" if value is not "1".
#
plural()
{
	local number="$1"
	
	
	if [ ! "$number" ]
	then
		ierror 'plural: invalid parameters'
	fi
	
	
	if [ "$number" -ne 1 ]
	then
		echo s
	fi
}


#
# Removes "'" from a stream.
#
makesafe()
{
	#tr -d "'"
	cat
}


#
# Removes file.
#
unlink_file()
{
	local file="$1"
	
	
	if [ ! "$file" ]
	then
		ierror 'unlink_file: invalid parameters'
	fi
	
	
	if [ -f "$file" ]
	then
		rm -f "$file"
	fi
}


#
# Securely create a file.
#
create_file()
{
	local file="$1"
	
	
	if [ ! "$file" ]
	then
		ierror 'create_file: invalid parameters'
	fi
	
	
	if [ -e "$file" ]
	then
		if [ ! -f "$file" ]
		then
			error "create_file: security violation for file '$file'"
		fi
	fi
	
	
	touch "$file"
}


#
# Prints error and exits.
#
error()
{
	local message="$1"
	
	
	if [ ! "$message" ]
	then
		ierror 'error: invalid parameters'
	fi
	
	
	echo "`prog`: $message" >&2
	
	
	cleanexit 1
}


#
# Prints internal error and exits.
#
ierror()
{
	local message="$1"
	
	
	if [ ! "$message" ]
	then
		ierror 'ierror: invalid parameters'
	fi
	
	
	echo "`prog`: internal error: $message" >&2
	
	
	cleanexit 1
}


#
# Prints notice.
#
notice()
{
	local message="$1"
	
	
	if [ ! "$message" ]
	then
		ierror 'notice: invalid parameters'
	fi
	
	
	echo "`prog`: $message" >&2
}


#
# Prints usage and exits.
#
usage()
{
	local message="$1"
	
	
	if [ ! "$message" ]
	then
		ierror 'usage: invalid parameters'
	fi
	
	
	echo "usage: `prog` $message" >&2
	
	
	cleanexit 1
}


#
# Does clean exit.
#
cleanexit()
{
	del_tempfile
	
	
	if [ ! "$use_lockf" ]
	then
		unlink_file "$lock_file"
	fi
	
	
	exit "${1:-0}"
}


#
# Prints error when command fails.
#
exitcode()
{
	local command="$1"
	local exit_code="$2"
	
	
	if [ ! "$command" -o ! "$exit_code" ]
	then
		ierror 'exitcode: invalid parameters'
	fi
	
	
	echo
	error "command '$command' failed with exit code $exit_code"
	echo
}


#
# Show command before executing it.
#
showcmd()
{
	local command="$1"
	
	
	if [ ! "$command" ]
	then
		ierror 'showcmd: invalid parameters'
	fi
	
	
	echo
	echo "> $command"
}


#
# Checks boolean configuration paramaters.
#
checkyesno()
{
	local variable="$1"
	
	
	if [ ! "$variable" ]
	then
		ierror 'checkyesno: invalid parameters'
	fi
	
	
	eval local value="\$$variable"
	
	
	case "$value"
	in
		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
			
			return 0
			;;
		
		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
			
			return 1
			;;
		
		*)
			error "variable '$variable' is not set properly in '$config_file'"
			;;
	esac
}


#
# Checks if command is failed
# (exit code is greater than zero).
#
cmd_failed()
{
	local exit_code="$1"
	
	
	if [ "$exit_code" -gt 0 ]
	then
		return 0
	else
		return 1
	fi
}


#
# Creates temporary file.
#
make_tempfile()
{
	local exit_code
	
	
	create_file "$temp_file"
	
	
	exit_code="$?"
	
	
	if cmd_failed "$exit_code"
	then
		error "cannot create temp file '$temp_file'"
	fi
}


#
# Deletes temporary file.
#
del_tempfile()
{
	unlink_file "$temp_file"
}


#
# Shows menu or checklist using dialog(1).
#
show_dialog()
{
	local stderr_file exitcode selected_options title message
	
	
	local type="$1"
	local name="$2"
	local text="$3"
	
	
	if [ "$#" -lt 4 ]
	then
		ierror 'show_dialog: invalid parameters'
	fi
	
	
	shift 3
	
	
	if [ ! "$type" ]
	then
		type=checklist
	fi
	
	
	stderr_file="$temp_file"
	
	
	case "$type"
	in
		checklist)
			;;
		
		menu)
			stderr_file=/dev/null
			;;
		
		*)
			ierror 'show_dialog: invalid type'
			;;
	esac
	
	
	if [ ! "$name" ]
	then
		ierror 'show_dialog: name is empty'
	fi
	
	if [ ! "$text" ]
	then
		ierror 'show_dialog: text is empty'
	fi
	
	if [ ! "$*" ]
	then
		ierror 'show_dialog: options is empty'
	fi
	
	
	if [ "$type" = checklist ]
	then
		make_tempfile
	fi
	
	
	if [ "`echo -n \"$*\" | wc -c`" -gt "$dialog_argmax" ]
	then
		error 'too many ports to show, use "find" command instead'
	fi
	
	
	title=" `prog`: $name "
	
	message="\n$text\n"
	
	
	if [ "$new_dialog" ]
	then
		bsddialog --output-separator ' ' --title "$title" "--$type" \
		"$message" 0 0 0 "$@" 2> "$stderr_file"
	else
		dialog --title "$title" "--$type" "$message" -1 -1 \
		"$dialog_list_rows" "$@" 2> "$stderr_file"
	fi
	
	
	exitcode="$?"
	
	
	if [ "$name" = confirm ]
	then
		selopt_confirm_exitcode="$exitcode"
	fi
	
	
	if [ "$type" = checklist ]
	then
		selected_options="`sed 's|\"||g; s|^ ||' \"$temp_file\"`"
		
		
		del_tempfile
		
		
		case "$name"
		in
			deinstall)
				
				selopt_deinstall_values="$selected_options"
				;;
			
			find)
				
				selopt_find_values="$selected_options"
				;;
			
			list)
				
				selopt_list_values="$selected_options"
				;;
			
			notrequired)
				
				selopt_notrequired_values="$selected_options"
				;;
		esac
	fi
}


#
# Shows confirm dialog.
#
show_confirm()
{
	local package comment count confirm_message cancel_message
	
	
	local type="$1"
	local packages="$2"
	
	
	if [ ! "$type" -o ! "$packages" ]
	then
		ierror 'show_confirm: invalid parameters'
	fi
	
	
	count=0
	
	
	set --
	
	
	for package in $packages
	do
		count="$(($count + 1))"
		
		
		comment=
		
		
		if [ "$dialog_empty_field_space" ]
		then
			comment=' '
		fi
		
		
		if [ "$type" = upgrade ]
		then
			if checkyesno upgrade_show_new_version
			then
				comment="`grep \"^$package\" \"$status_file\" | awk '{ print \"-> \"\$2 }'`"
			fi
		fi
		
		
		set -- "$@" "$package" "$comment"
	done
	
	
	case "$type"
	in
		upgrade)
			confirm_message="Upgrade $count package`plural \"$count\"`?"
			
			cancel_message='Upgrade cancelled'
			;;
		
		deinstall)
			confirm_message="Deinstall $count package`plural \"$count\"`?"
			confirm_message="$confirm_message\nARE YOU SURE?"
			
			cancel_message='Deinstall cancelled'
			;;
		
		install)
			confirm_message="Install $count package`plural \"$count\"`?"
			
			cancel_message='Install cancelled'
			;;
		
		*)
			ierror 'show_confirm: invalid type'
			;;
	esac
	
	
	show_dialog menu confirm "$confirm_message" "$@"
	
	
	if cmd_failed "$selopt_confirm_exitcode"
	then
		echo
		echo "$cancel_message"
		echo
		
		
		cleanexit
	fi
}


#
# Shows packages before executing
# install, upgrade or deinstall.
#
show_packages()
{
	local package
	
	
	local message="$1"
	local packages="$2"
	
	
	if [ ! "$message" -o ! "$packages" ]
	then
		ierror 'show_packages: invalid parameters'
	fi
	
	
	echo
	echo "$message:"
	echo
	
	
	for package in $packages
	do
		echo "$package"
	done
	
	
	echo
}


#
# Shows package deinstall dialog.
#
package_deinstall()
{
	local command exitcode
	
	
	local packages="$1"
	
	
	if [ ! "$packages" ]
	then
		echo
		echo 'No packages selected for deinstall'
		echo
		
		
		cleanexit
	fi
	
	
	show_confirm deinstall "$packages"
	
	
	if checkyesno use_portupgrade
	then
		command="${root_command_prefix}pkg_deinstall $packages"
		
	elif checkyesno use_pkgng
	then
		command="${root_command_prefix}pkg delete $packages"
	else
		error 'package_deinstall: enable use_portupgrade or use_pkgng'
	fi
	
	
	if ! checkyesno enable_real_deinstall
	then
		cat <<- EOF
		
		Execute that manually:
		
		$command
		
		EOF
		
		cleanexit
	fi
	
	
	show_packages Deinstalling "$packages"
	
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" $exitcode
	else
		if checkyesno listup_after_deinstall
		then
			echo
			
			
			do_list
		fi
	fi
	
	
	cleanexit
}


#
# Command:
#  shows help. for more info, look EOF.
#
do_help()
{
	cat <<- EOF
	
	Help of `prog` is now moved to man page,
	see pkgfe(8)
	
	EOF
	
	
	cleanexit
}


#
# Command:
#  shows list of outdated packages.
#
do_list()
{
	local pkg_info_command portupgrade_extra_arg command exitcode count
	
	
	local mode="$1"
	
	
	if checkyesno use_portupgrade
	then
		if checkyesno use_pkgng
		then
			pkg_info_command='pkg info -E portupgrade>=2.4.6,2'
		else
			pkg_info_command='pkg_info -E portupgrade>=2.4.6,2'
		fi
		
		
		$pkg_info_command > /dev/null
		
		
		exitcode="$?"
		
		
		if ! cmd_failed "$exitcode"
		then
			portupgrade_extra_arg=F
		fi
		
		
		command="${root_command_prefix}portversion -${portupgrade_extra_arg}vl <"
		
	elif checkyesno use_pkgng
	then
		command="${root_command_prefix}pkg version -vl <"
	else
		error 'do_list: enable use_portupgrade or use_pkgng'
	fi
	
	
	make_tempfile
	
	
	showcmd "$command"
	
	$command > "$temp_file"
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		del_tempfile
		
		
		exitcode "$command" "$exitcode"
	fi
	
	
	sed 's|<||; s|needs updating (.* has ||; s|) *$||; s|) (| |; s| => .*$||' \
	< "$temp_file" | makesafe > "$status_file"
	
	
	del_tempfile
	
	
	if ! checkyesno ignore_held
	then
		make_tempfile
		
		
		cat "$status_file" > "$temp_file"
		
		
		fgrep -v '[held]' < "$temp_file" > "$status_file"
		
		
		del_tempfile
	fi
	
	
	count="`wc -l \"$status_file\" | awk '{ print \$1 }'`"
	
	
	echo
	echo '-----------------------------------'
	echo "Found $count outdated package`plural \"$count\"`"
	echo '-----------------------------------'
	echo
	
	
	if [ "$count" -eq 0 -a "$mode" = exit-if-none ]
	then
		cleanexit
	fi
}


#
# Command:
#  updates ports tree using csup(1).
#
do_csup()
{
	local command exitcode
	
	
	check_supfile
	
	
	command="${root_command_prefix}csup $csup_args $csup_supfile"
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	command="${root_command_prefix}portsdb -Fu"
	
	echo
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	echo
	
	do_list
}


#
# Command:
#  updates ports tree using portsnap(8).
#
do_portsnap()
{
	local command exitcode
	
	
	command="${root_command_prefix}portsnap fetch"
	
	
	if ! checkyesno old_portsnap
	then
		command="$command update"
	fi
	
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	if checkyesno old_portsnap
	then
		command="${root_command_prefix}portsnap update"
		
		
		echo
		
		showcmd "$command"
		
		$command
		
		
		exitcode="$?"
		
		
		if cmd_failed "$exitcode"
		then
			exitcode "$command" "$exitcode"
		fi
	fi
	
	
	command="${root_command_prefix}portsdb -u"
	
	
	echo
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	echo
	
	do_list
}


#
# Command:
#  fixes package database.
#
do_fixdb()
{
	local command exitcode
	
	
	command="${root_command_prefix}pkgdb -F"
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed $exitcode
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	echo
	
	cleanexit
}


#
# Command:
#  does search in ports tree.
#
do_find()
{
	local search_string="$1"
	
	
	local type=name
	
	
	case "$search_string"
	in
		name|key)
			search_string="$2"
			
			type="$1"
			;;
	esac
	
	
	if [ ! "$search_string" ]
	then
		usage 'find [name|key] [regex]'
	fi
	
	
	make_tempfile
	
	
	if checkyesno use_pkgng_repo
	then
		if [ "$type" != name ]
		then
			del_tempfile
			
			
			error 'not supported when use_pkgng_repo is enabled'
		fi
		
		
		pkg search -ix -S pkg-name -L pkg-name -Q categories \
		-Q maintainer -Q www -Q comment -Q depends-on \
		"$search_string" > "$temp_file"
	else
		showcmd "cd $ports_path && make search $type=$search_string"
		
		cd "$ports_path" || cleanexit 1
		
		
		make search "$type=$search_string" > "$temp_file"
	fi
	
	
	if [ ! -s "$temp_file" ]
	then
		del_tempfile
		
		echo
		
		error "no matches for '$search_string'"
	fi
	
	
	clear
	
	$pager_program "$temp_file"
	
	
	del_tempfile
	
	
	cleanexit
}


#
# Command:
#  does "make config" in port's directory.
#
do_config()
{
	local portpath
	
	
	local portname="$1"
	
	
	if [ ! "$portname" ]
	then
		usage 'c [port]'
	fi
	
	
	portpath="`whereis -sB \"$ports_path\" -f \"$portname\" | cut -d ' ' -f 2`"
	
	
	if [ ! -d "$portpath" ]
	then
		error "port '$portname' not found"
	fi
	
	
	showcmd "cd $portpath && make config"
	
	cd "$portpath" || cleanexit 1
	
	${root_command_prefix}make config
	
	
	cleanexit
}


#
# Command:
#  does search in ports tree
#  and shows install dialog.
#
do_find_install()
{
	local index_file name comments count awk message
	
	
	local search_string="$1"
	
	
	if ! checkyesno use_pkgng_repo
	then
		index_file="$ports_path/INDEX-`uname -r | cut -d . -f 1`"
		
		
		if [ ! -f "$index_file" ]
		then
			if [ -f "$ports_path/INDEX" ]
			then
				index_file="$ports_path/INDEX"
			else
				error "no valid INDEX file found under '$ports_path'"
			fi
		fi
	fi
	
	
	if [ ! "$search_string" ]
	then
		usage 'findi [regex]'
	fi
	
	
	make_tempfile
	
	
	if [ "$new_dialog" ]
	then
		awk='{ print $1 " " $2 }'
	else
		awk="{ print \$1\" \"(length(\$2) > $findi_description_length"
		awk="$awk ? substr(\$2, 0, $findi_description_length)\"...\""
		awk="$awk : \$2) }"
	fi
	
	
	if checkyesno use_pkgng_repo
	then
		pkg rquery '%n-%v|%c' | makesafe \
		| egrep -i -- "$search_string" | awk -F '|' "$awk" \
		> "$temp_file"
	else
		cut -d '|' -f 1,4 "$index_file" | makesafe \
		| egrep -i -- "$search_string" | awk -F '|' "$awk" \
		> "$temp_file"
	fi
	
	
	if [ ! -s "$temp_file" ]
	then
		del_tempfile
		
		
		error "no packages matching '$search_string' found"
	fi
	
	
	count="`wc -l \"$temp_file\" | awk '{ print \$1 }'`"
	
	
	echo
	echo "$count result`plural \"$count\"` found. Generating dialog..."
	echo
	
	
	set --
	
	
	while read name comments
	do
		set -- "$@" "$name" "$comments" OFF
	done \
	< "$temp_file"
	
	
	del_tempfile
	
	
	message="Found $count package`plural \"$count\"`."
	message="$message\nSelect one`plural \"$count\"` you want to install"
	
	
	show_dialog '' find "$message" "$@"
	
	
	if [ ! "$selopt_find_values" ]
	then
		echo
		echo 'No packages selected for install'
		echo
		
		
		cleanexit
	fi
	
	
	show_confirm install "$selopt_find_values"
	
	
	do_portinstall_portupgrade portinstall "$selopt_find_values"
}


#
# Command:
#  searches for local packages.
#
do_ls()
{
	local search_string="$1"
	
	
	if [ ! "$search_string" ]
	then
		usage 'ls [regex]'
	fi
	
	
	if checkyesno use_pkgng
	then
		pkg info -q | egrep -i -- "$search_string"
	else
		ls -1 "$pkgdb_path" | grep -v '^pkgdb\.' \
		| egrep -i -- "$search_string"
	fi
	
	
	if cmd_failed "$?"
	then
		error "no packages matching '$search_string' found"
	fi
	
	
	cleanexit
}


#
# Command:
#  deinstalls not required packages.
#
do_notreq()
{
	local old_wd name count message comment
	
	
	count=0
	
	
	set --
	
	
	if checkyesno use_pkgng
	then
		for name in `pkg query -e %#r=0 %n-%v | makesafe`
		do
			count="$(($count + 1))"
			
			
			set -- "$@" "$name" 'no reverse dependencies' OFF
		done
	else
		old_wd="`pwd`"
		
		
		cd "$pkgdb_path" || cleanexit 1
		
		
		for name in `ls -1 | grep -v '^pkgdb\.' | makesafe`
		do
			count="$(($count + 1))"
			
			
			comment=
			
			
			if [ ! -f "$name/+REQUIRED_BY" ]
			then
				comment='no +REQUIRED_BY'
				
			elif [ ! -s "$name/+REQUIRED_BY" ]
			then
				comment='empty +REQUIRED_BY'
			fi
			
			
			set -- "$@" "$name" "$comment" OFF
		done
		
		
		cd "$old_wd" || cleanexit 1
	fi
	
	
	message="Found $count not required package`plural \"$count\"`"
	message="$message\nSelect one`plural \"$count\"` you want to deinstall"
	
	
	show_dialog '' notrequired "$message" "$@"
	
	
	package_deinstall "$selopt_notrequired_values"
}


#
# Command:
#  deinstalls packages.
#
do_deinst()
{
	local old_wd name count comment message
	
	
	count=0
	
	
	set --
	
	
	if checkyesno use_pkgng
	then
		make_tempfile
		
		
		pkg query '%n-%v %#r' | makesafe > "$temp_file"
		
		
		while read name reverse_dependencies_count
		do
			count="$(($count + 1))"
			
			
			comment=
			
			
			if [ "$dialog_empty_field_space" ]
			then
				comment=' '
			fi
			
			
			if [ "$reverse_dependencies_count" -gt 0 ]
			then
				comment='Required by'
				comment="$comment $reverse_dependencies_count"
				comment="$comment other"
				comment="$comment`plural \"$reverse_dependencies_count\"`"
			fi
			
			
			set -- "$@" "$name" "$comment" OFF
		done \
		< "$temp_file"
		
		
		del_tempfile
	else
		old_wd="`pwd`"
		
		
		cd "$pkgdb_path" || cleanexit 1
		
		
		for name in `ls -1 | grep -v '^pkgdb\.' | makesafe`
		do
			count="$(($count + 1))"
			
			
			comment=
			
			
			if [ "$dialog_empty_field_space" ]
			then
				comment=' '
			fi
			
			
			if [ -f "$name/+REQUIRED_BY" ]
			then
				comment='Required by others'
			fi
			
			
			set -- "$@" "$name" "$comment" OFF
		done
		
		
		cd "$old_wd" || cleanexit 1
	fi
	
	
	message="Found $count package`plural \"$count\"`."
	message="$message\nSelect one`plural \"$count\"` you want to deinstall"
	
	
	show_dialog '' deinstall "$message" "$@"
	
	
	package_deinstall "$selopt_deinstall_values"
}


#
# Command:
#  updates portaudit database.
#
do_padb()
{
	local command exitcode
	
	
	if checkyesno use_pkgng
	then
		command="${root_command_prefix}pkg audit --fetch"
	else
		command="${root_command_prefix}portaudit -Fd"
	fi
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	echo
}


#
# Command:
#  shows upgrade dialog.
#
do_upgrade()
{
	local name comments count message
	
	
	if [ ! -s "$status_file" ]
	then
		do_list exit-if-none
	fi
	
	
	count="`wc -l \"$status_file\" | awk '{ print \$1 }'`"
	
	
	set --
	
	
	while read name comments
	do
		set -- "$@" "$name" "< $comments" OFF
	done \
	< "$status_file"
	
	
	message="Found $count outdated package`plural \"$count\"`"
	message="$message\nSelect one`plural \"$count\"` you want to upgrade"
	
	
	show_dialog '' list "$message" "$@"
	
	
	if [ ! "$selopt_list_values" ]
	then
		echo
		echo 'No packages selected for upgrade'
		echo
		
		
		cleanexit
	fi
	
	
	show_confirm upgrade "$selopt_list_values"
	
	
	do_portinstall_portupgrade portupgrade "$selopt_list_values"
}


#
# Command:
#  runs portinstall(1) or portupgrade(1) with
#  verbose mode and additional arguments.
#
do_portinstall_portupgrade()
{
	local message command exitcode
	
	
	local program="$1"
	
	local arguments="$2"
	
	
	case "$program"
	in
		portinstall)
			message=Installing
			;;
		
		portupgrade)
			message=Upgrading
			;;
		
		*)
			ierror 'do_portinstall_portupgrade: invalid program'
			;;
	esac
	
	
	if [ ! "$arguments" ]
	then
		ierror 'do_portinstall_portupgrade: arguments cannot be empty'
	fi
	
	
	show_packages "$message" "$arguments"
	
	
	if checkyesno update_portaudit_db
	then
		do_padb
	fi
	
	
	if checkyesno use_portupgrade
	then
		command="$root_command_prefix$program $portupgrade_args $arguments"
		
	elif checkyesno use_pkgng
	then
		command="${root_command_prefix}pkg ${program#port*} $arguments"
	else
		error 'do_portinstall_portupgrade: enable use_portupgrade or use_pkgng'
	fi
	
	
	showcmd "$command"
	
	$command
	
	
	exitcode="$?"
	
	
	if cmd_failed "$exitcode"
	then
		exitcode "$command" "$exitcode"
	fi
	
	
	if [ "$program" = portupgrade ]
	then
		if checkyesno listup_after_upgrade
		then
			echo
			
			do_list
		fi
	fi
	
	
	cleanexit
}


#
# Command:
#  checks for existence of required programs.
#
do_checkbinaries()
{
	local binary exitcode
	
	
	exitcode=0
	
	
	echo 'Checking for existence of binaries we need...'
	echo
	
	
	for binary in portupgrade portinstall portversion dialog csup \
	portsdb pkgdb portaudit pkg_deinstall sed grep cat touch id \
	uname cut portsnap awk wc whereis pkg tr dialog bsddialog
	do
		
		which "$binary" > /dev/null
		
		
		if cmd_failed "$?"
		then
			notice "$binary: program not found"
			
			exitcode=1
		else
			echo "$binary: OK"
		fi
	done
	
	
	cleanexit "$exitcode"
}


#
# Command:
#  shows configuration.
#
do_showconfig()
{
	local default
	
	
	if [ ! -f "$config_file" ]
	then
		notice "configuration file '$config_file' is not found"
		
		default=1
		
	elif [ ! "$root_command_prefix" -a ! -r "$config_file" ]
	then
		notice "configuration file '$config_file' is not readable"
		
		default=1
	fi
	
	
	echo
	
	
	if [ "$default" ]
	then
		echo 'using default configuration:'
	else
		echo "$config_file:"
	fi
	
	
	cat <<- EOF
	---------------------------------------------------------------------
	
	portupgrade_logdir="$portupgrade_logdir"
	
	portupgrade_args="$portupgrade_args"
	
	csup_supfile="$csup_supfile"
	
	csup_args="$csup_args"
	
	root_command_prefix="$root_command_prefix"
	
	status_file="$status_file"
	
	lock_file="$lock_file"
	
	temp_file="$temp_file"
	
	dialog_list_rows="$dialog_list_rows"
	
	findi_description_length="$findi_description_length"
	
	listup_after_upgrade="$listup_after_upgrade"
	
	ignore_held="$ignore_held"
	
	update_portaudit_db="$update_portaudit_db"
	
	enable_real_deinstall="$enable_real_deinstall"
	
	listup_after_deinstall="$listup_after_deinstall"
	
	old_portsnap="$old_portsnap"
	
	upgrade_show_new_version="$upgrade_show_new_version"
	
	use_pkgng="$use_pkgng"
	
	use_pkgng_repo="$use_pkgng_repo"
	
	use_portupgrade="$use_portupgrade"
	
	---------------------------------------------------------------------
	EOF
	
	
	cleanexit
}


#
# Command:
#  installs portupgrade from ports if it's not found.
#
do_init()
{
	local portupgrade_port_path
	
	
	which portupgrade > /dev/null
	
	
	if cmd_failed "$?"
	then
		echo 'portupgrade not found, installing'
		
		
		portupgrade_port_path="`whereis -qs portupgrade`"
		
		
		if cmd_failed "$?"
		then
			error 'cannot find portupgrade port path'
		else
			make -C "$portupgrade_port_path" config-recursive \
			fetch-recursive clean install clean
			
			
			if cmd_failed "$?"
			then
				error 'installing portupgrade from ports failed'
			else
				echo 'portupgrade installed successfully'
			fi
		fi
	else
		echo 'portupgrade already exists'
	fi
	
	
	cleanexit
}


#
# Checks boolean variables in configuration file.
#
check_boolean_variables()
{
	local variable
	
	
	for variable in listup_after_upgrade ignore_held \
	update_portaudit_db enable_real_deinstall \
	listup_after_deinstall old_portsnap upgrade_show_new_version
	do
		checkyesno "$variable"
	done
}


#
# Checks numeric variables in configuration file.
#
check_numeric_variables()
{
	local variable value
	
	
	for variable in dialog_list_rows findi_description_length
	do
		eval value="\$$variable"
		
		case "$value"
		in
			[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
				;;
			
			*)
				error "variable '$variable' has invalid or not numeric value in '$config_file'"
				;;
		esac
	done
}


#
# Checks (and creates) status file.
#
check_statusfile()
{
	if [ ! -f "$status_file" ]
	then
		create_file "$status_file"
		
		if cmd_failed $?
		then
			error "cannot create status file '$status_file'"
		fi
	fi
}


#
# Checks csup supfile.
#
check_supfile()
{
	if [ ! "$csup_supfile" ]
	then
		error "please define csup_supfile in '$config_file'"
		
	elif [ ! -f "$csup_supfile" ]
	then
		error "supfile '$csup_supfile' is not found"
		
	elif [ ! "$root_command_prefix" -a ! -r "$csup_supfile" ]
	then
		error "supfile '$csup_supfile' is not readable"
	fi
}


#
# Checks portupgrade log directory.
#
check_portupgrade_logdir()
{
	if [ ! "$portupgrade_logdir" ]
	then
		return 0
	fi
	
	
	if [ ! -d "$portupgrade_logdir" ]
	then
		notice "portupgrade log directory '$portupgrade_logdir' is not found"
		
	elif [ ! "$root_command_prefix" -a ! -w "$portupgrade_logdir" ]
	then
		notice "portupgrade log directory '$portupgrade_logdir' is not writable"
	else
		return 0
	fi
	
	
	cat <<- EOF
	
	If you don't want logging,
	comment out following varibles in $config_file:
	
	portupgrade_logdir
	portupgrade_args
	EOF
	
	
	cleanexit 1
}


#
# Checks (and creates) lock file.
#
check_lockfile()
{
	if [ -f "$lock_file" ]
	then
		notice "lock file '$lock_file' exists"
		
		cat <<- EOF
		
		This probably means that you have
		another `prog` session running.
		If there are no active sessions,
		then it's probably a result of
		unfinished session. In this case,
		you must delete the file manually,
		before you can run `prog` again.
		EOF
		
		exit 1
	fi
	
	
	create_file "$lock_file"
	
	
	if cmd_failed "$?"
	then
		error "cannot create lock file '$lock_file'"
	fi
}


#
# Checks for privileges.
#
check_privs()
{
	if [ "`id -u`" -gt 0 ]
	then
		if [ ! "$root_command_prefix" ]
		then
			notice 'please switch to root'
			
			
			cat <<- EOF
			
			Alternatively, define root_command_prefix
			in $config_file
			EOF
			
			
			cleanexit 1
		fi
	else
		root_command_prefix=
	fi
}


###############################################################################

if [ -f "$config_file" -a -r "$config_file" ]
then
	. "$config_file"
fi


if [ "`uname -s`" != FreeBSD ]
then
	error 'requires FreeBSD'
fi


freebsd_version="`uname -K 2> /dev/null || true`"


stty_size_rows="`stty size 2> /dev/null | cut -d ' ' -f 1`"

stty_size_cols="`stty size 2> /dev/null | cut -d ' ' -f 2`"


dialog_argmax="$((`sysctl -n kern.argmax` - 1024))"


default_dialog_list_rows=10
default_findi_description_length=26

default_use_pkgng=NO
default_use_pkgng_repo=NO

default_use_portupgrade=YES

dialog_empty_field_space=1

new_dialog=

use_lockf=1


case "$stty_size_rows"
in
	[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
		default_dialog_list_rows="$(($stty_size_rows - 20))"
		;;
esac

case "$stty_size_cols"
in
	[0-9]|[0-9][0-9]|[0-9][0-9][0-9])
		default_findi_description_length="$(($stty_size_cols / 2))"
		;;
esac


if [ -z "$freebsd_version" ]
then
	freebsd_version=0
fi


if [ "$freebsd_version" -ge 1000000 ]
then
	default_use_pkgng=YES
	default_use_pkgng_repo=YES
	
	default_use_portupgrade=NO
fi


if [ "$freebsd_version" -ge 1300000 ]
then
	dialog_empty_field_space=
fi


if [ "$freebsd_version" -ge 1400000 ]
then
	new_dialog=1
fi


: ${portupgrade_logdir=""}
: ${portupgrade_args="-vc"}
: ${csup_supfile=""}
: ${csup_args="-L 2"}
: ${root_command_prefix="sudo "}
: ${status_file="$temp_dir/`prog`.status"}
: ${lock_file="$temp_dir/`prog`.lock"}
: ${temp_file="$temp_dir/`prog`.tmp"}
: ${dialog_list_rows="$default_dialog_list_rows"}
: ${findi_description_length="$default_findi_description_length"}
: ${listup_after_upgrade="YES"}
: ${ignore_held="YES"}
: ${update_portaudit_db="NO"}
: ${enable_real_deinstall="NO"}
: ${listup_after_deinstall="YES"}
: ${old_portsnap="NO"}
: ${upgrade_show_new_version="NO"}
: ${use_pkgng="$default_use_pkgng"}
: ${use_pkgng_repo="$default_use_pkgng_repo"}
: ${use_portupgrade="$default_use_portupgrade"}


if [ "$use_lockf" ]
then
	if [ "${1:-}" = lockf ]
	then
		shift
	else
		lockf -t 0 "$lock_file" "$0" lockf $*
		
		
		cleanexit
	fi
else
	check_lockfile
fi


trap 'cleanexit 1' 1 2 15 30 31


check_boolean_variables

check_numeric_variables

check_privs


check_portupgrade_logdir


check_statusfile


###############################################################################

case "$1"
in
	'')
		do_upgrade
		;;
	
	listup|list)
		
		do_list
		cleanexit
		;;
	
	cvsup|cvs|csup)
		
		do_csup
		cleanexit
		;;
	
	portsnap|snap)
		
		do_portsnap
		cleanexit
		;;
	
	all)
		do_csup
		do_upgrade
		;;
	
	snap+run)
		
		do_portsnap
		do_upgrade
		;;
	
	listup+run|list+run)
		
		do_list
		do_upgrade
		;;
	
	fixdb|fix)
		
		do_fixdb
		;;
	
	find|portfind)
		
		shift
		
		do_find "$1" "$2"
		;;
	
	config|conf|c)
		
		shift
		
		do_config "$1"
		;;
	
	findi|findv)
		
		shift
		
		do_find_install "$1"
		;;
	
	ls|lspkg)
		
		shift
		
		do_ls "$1"
		;;
	
	chkreq|notreq|noreq)
		
		do_notreq
		;;
	
	deinst|deins)
		
		do_deinst
		;;
	
	padb)
		do_padb
		cleanexit
		;;
	
	install|i)
		
		shift
		
		if [ ! "$*" ]
		then
			usage 'i [port]'
		fi
		
		do_portinstall_portupgrade portinstall "$*"
		;;
	
	upgrade|u)
		
		shift
		
		if [ ! "$*" ]
		then
			usage 'u [port]'
		fi
		
		do_portinstall_portupgrade portupgrade "$*"
		;;
	
	chkbin)
		do_checkbinaries
		;;
	
	showconf)
		
		do_showconfig
		;;
	
	init)
		
		do_init
		;;
	
	help)
		do_help
		;;
	
	*)
		error "invalid parameter '$1'"
		;;
esac


cleanexit
