#!/bin/sh -e
# ==================================================================================
# portsreinstall main script
# Copyright (C) 2010, 2011 Mamoru Sakaue, MwGhennndo, All Rights Reserved.
# ==================================================================================

# ================================================
# ================ PREPARATION ===================
# ================================================

APPNAME=`basename $0`
MYVERSION=0.9.4

PREFIX=${PREFIX:-/usr/local}
CONFFILE=${PREFIX}/etc/${APPNAME}.conf
DBDIR=/var/tmp/${APPNAME}.db
PKGTOOLSCONF=${PREFIX}/etc/pkgtools.conf

# ============= Option check =============
help_mode=0
oprion_err=0
target_ports=
taboo_ports=
load_pkgtoolsconf=no
show_version=no
avoid_vulner=no
while :
do
	if [ "$1" = "-h" ]
	then
		help_mode=1
		shift
	elif [ "$1" = "-H" ]
	then
		help_mode=2
		shift
	elif [ "$1" = "-t" ]
	then
		target_ports=$target_ports,$2
		shift 2
	elif [ "$1" = "-x" ]
	then
		taboo_ports=$taboo_ports,$2
		shift 2
	elif [ "$1" = "-p" ]
	then
		load_pkgtoolsconf=default
		shift
	elif [ "$1" = "-P" ]
	then
		load_pkgtoolsconf=override
		shift
	elif [ "$1" = "-V" ]
	then
		show_version=yes
		shift
	elif [ "$1" = "-s" ]
	then
		avoid_vulner=yes
		shift
	else
		break
	fi
done

# if [ $# -gt 0 ]
# then
# 	oprion_err=1
# fi

credit ()
{
	echo "${APPNAME} version ${MYVERSION}"
	echo " -- Ports upgrading utility for massive forced reinstalltion"
	echo " -- And for those who are pursuing the perfect packages environment"
	echo "Copyright (C) 2010, 2011 Mamoru Sakaue, MwGhennndo, All Rights Reserved."
	echo "Email: <sakaue.mamoru@samurai.mwghennn.net>"
	echo "Homepage: <http://www.mwghennndo.com/softwares/portsreinstall/>"
}

# Usage
if [ $help_mode -eq 1 -o $oprion_err -eq 1 ]
then
	echo "USAGE: ${APPNAME} [OPTIONS] [command]" >&2
	echo >&2
	echo "[OPTIONS]" >&2
	echo " -h : Show this short help" >&2
	echo " -H : Show long help" >&2
	echo " -V : uhow the current version" >&2
	echo " -t glob1[,glob2,...] : Set target port glob(s)" >&2
	echo " -x glob1[,glob2,...] : Set the port glob(s) to be taboo" >&2
	echo " -p : Import settings from pkgtools.conf as the default" >&2
	echo " -P : Import settings from pkgtools.conf as the override" >&2
	echo " -s : Build of vulnerable ports are avoided by triggering errors" >&2
	echo >&2
	echo "[ARGUMENTS]" >&2
	echo " command: do (default) | prepare | redo | clean | ok add globs..." >&2
	echo "          | ok del globs... | taboo add globs... | taboo del globs..." >&2
	echo "          | save [dir] | load path" >&2
	echo "          | show todo | show done | show resolved | show failure | show taboo" >&2
	echo >&2
	echo "[DESCRIPTIONS]" >&2
	echo " This utility is an alternative to portupgrade(1) and portmaster(8), and" >&2
	echo "designed to be suitable for reinstallation of all packages afrer major version" >&2
	echo "upgrade of the system or very long absence of ports upgrade." >&2
	echo " Usually, execute with no option nor argument, or with '-p' or '-P' if you" >&2
	echo "have installed portupgrade(1)." >&2
	exit $oprion_err
elif [ $help_mode -eq 2 ]
then
	credit >&2
	echo >&2
	echo "USAGE: ${APPNAME} [OPTIONS] [command]" >&2
	echo >&2
	echo "[OPTIONS]" >&2
	echo " -h : Show short help" >&2
	echo " -H : Show this long help" >&2
	echo " -V : Show the current version" >&2
	echo " -t glob1[,glob2,...] : Set target port glob(s)" >&2
	echo "        This option implies reconstruction of the temporal database." >&2
	echo "        This option is ignored when you restart the last-aborted process." >&2
	echo " -x glob1[,glob2,...] : Set the port glob(s) to be taboo" >&2
	echo "        This option registers a port to be ignored as taboo." >&2
	echo "        Mainly for ports that show terrible behaviors resulting in system crash." >&2
	echo "        You can give this option also when you restart an aborted process." >&2
	echo "        Alternatively you can do the same thing by 'taboo' command." >&2
	echo "        If you want to register permenently, set to the configuration file." >&2
	echo " -p : Import settings from pkgtools.conf as the default" >&2
	echo "        This option is ignored when you restart the last-aborted process." >&2
	echo "        (Requires ports-mgmt/portupgrade*)" >&2
	echo " -P : Import settings from pkgtools.conf as the override" >&2
	echo "        This option is ignored when you restart the last-aborted process." >&2
	echo "        (Requires ports-mgmt/portupgrade*)" >&2
	echo " -s : Build of vulnerable ports are avoided by triggering errors" >&2
	echo "        Note that already installed vulnerable packages are untouched." >&2
	echo "        If you desire to uninstall them, do it manually." >&2
	echo "        This specification will be reasonable for the practicle situations." >&2
	echo >&2
	echo "[ARGUMENTS]" >&2
	echo " command: (For optional operations or confirmation)" >&2
	echo "      {one of the following commands}" >&2
	echo "      do : full execution (default)" >&2
	echo "      prepare : stop before the actual operations to the ports/packages" >&2
	echo "      redo : execute again for failed ports and their dependents" >&2
	echo "      clean : clean up the temporal database" >&2
	echo "      ok add glob1 [[,]glob2 ...] : register manually resolved ports" >&2
	echo "      ok del glob1 [[,]glob2 ...] : deregister manually resolved ports" >&2
	echo "      taboo add glob1 [[,]glob2 ...]: register taboo ports" >&2
	echo "      taboo del glob1 [[,]glob2 ...]: deregister taboo ports" >&2
	echo "      save [dir] : save the current temporal database as a .tar.gz archive" >&2
	echo "      load path : load a temporal database archive" >&2
	echo "      show [arg] : show the list of ports to be reinstalled" >&2
	echo "              todo : ports to be reinstalled (default)" >&2
	echo "              done : already reinstalled ports" >&2
	echo "              resolved : manually reinstalled ports" >&2
	echo "              failure : failed ports" >&2
	echo "              taboo : taboo ports" >&2
	echo >&2
	echo "[CONFIGURATION FILE]" >&2
	echo "          ${CONFFILE}" >&2
	echo >&2
	echo "[NOTATIONS]" >&2
	echo " The 'glob' is either of pkgname_glob or portorigin_glob explained in the man" >&2
	echo "page of portsdb(1) and ports_glob(1): for example, zip-3.0, zip-*, and archivers" >&2
	echo "/zip. Here the wild card symbol '*' must be quoted or escaped." >&2
	echo >&2
	echo "[DESCRIPTIONS]" >&2
	echo " This utility realizes smart reinstallation of a large number of ports by" >&2
	echo "allowing you to run when when the machine is free and abort when busy." >&2
	echo " Concretely, you can stop the process by CTRL+C anytime and restart quickly." >&2
	echo " This functionality allows you, for example, to start this utility before lunch," >&2
	echo "abort after lunch, restart before dinner, abort after dinner, restart before" >&2
	echo "going to bed, abort after breakfast, restart before lunch, ..., and finally" >&2
	echo "complete." >&2
	echo >&2
	echo " The pocily of this utility is to complete the job by the most automatic and" >&2
	echo "finally successful way even if it will take a long time in total." >&2
	echo " All missing build- and run-time dependencies are newly installed if any." >&2
	echo " All reinstallation processes are tried to be proceeded forcedly by ignoring" >&2
	echo "errors or vulnerabilities as possible." >&2
	echo >&2
	echo " The algorithms of this utility are customized for massive reinstallation to" >&2
	echo "be invoked after major upgrade of the system where rebuild of all third-party" >&2
	echo "applications are encouraged before cleaning up obsolete system libraries." >&2
	echo " Nevertheless, the all functionalities of this utility is applicable to any" >&2
	echo "situations where complete reinstalltion of the all or parts of ports is" >&2
	echo "preferred, e.g., when you have been lazy in upgrade of ports for too long time." >&2
	echo " For the usual purposes of upgrading packages installed by ports, you are" >&2
	echo "recommended to use ports-mgmt/portupgrade or ports-mgmt/portmaster instead." >&2
	echo >&2
	echo " The scheme of this utility is divided into the temporal database construction" >&2
	echo "phase and the reinstalltion phase." >&2
	echo " Execution by 'portsreinstall prepare' procedes to the end the first phase, and" >&2
	echo "that without any argumet procedes to the end of the second phase." >&2
	echo " Each of these two major phases is divided into minor phases." >&2
	echo " When the latest aborted process is restarted, completed minor phases are" >&2
	echo "skipped.">&2
	echo >&2
	echo " The massive minor phases belong to 'collecting dependencies of installed/" >&2
	echo "missing packages', 'ordering dependencies' for the first major phase and " >&2
	echo "'reinstallation' 'package database update' for the second." >&2
	echo " Most of them are divided into more minor phases except for" >&2
	echo "'package database update'." >&2
	echo >&2
	echo " When option(s) '-t' is/are given, the temporal database is reconstructed so" >&2
	echo "that only the target and its required and dependent ports are reinstalled (for" >&2
	echo "restarting the latest aborted process, execute without any options except for" >&2
	echo "'-x' nor arguments)." >&2
	echo >&2
	echo " The user is encouraged to run this utility under script(1) so as to record all" >&2
	echo "logs in order to resolve problems that you may (rather 'will', practically)" >&2
	echo "encounter." >&2
	echo " The solutions depend on the indiviual cases." >&2
	echo " If the problem will be resolved by reconfiguration of the port option, execute" >&2
	echo "'make config' at the corresponding port directory, and then restart this" >&2
	echo "utility." >&2
	echo " If the problem will be resolved by manual fetch of tarballs, do it and then" >&2
	echo "restart this utility." >&2
	echo " If the problem will be resolved by deleting a concerned package, do it by" >&2
	echo "pkg_delete(1) with '-f' option or execute 'make deinstall' at the corresponding" >&2
	echo "port directory, execute '${APPNAME} ok add \$glob' where '\$glob' is the ports" >&2
	echo "glob of the concerned port, and then restart this utility." >&2
	echo " If the problem will be resolved by manual reinstallation using pkg_add(1) or so" >&2
	echo "on, do it and execute '${APPNAME} ok add \$glob' where '\$glob' is the ports" >&2
	echo "glob of the concerned port, and then restart this utility by 'redo' command." >&2
	echo >&2
	echo " If you are familiar to the mechanism of Ports Collections system, in order to" >&2
	echo "minimize package conflictions, it may be a good idea to delete packages which " >&2
	echo "you don't think necessary before starting to use this utility." >&2
	echo "Don't be afraid to delete necessary dependencies because all required ports are" >&2
	echo "automatically installed." >&2
	echo >&2
	echo " If you run into confusion, it may be a good idea to clean up the temporal" >&2
	echo "database by executing '${APPNAME} clean' and start again from the first." >&2
	exit
fi

if [ $show_version = yes ]
then
	echo "${APPNAME} version ${MYVERSION}"
	exit
fi

command=$1
[ -n "$command" ] && shift

# ====================================================
# ================== FUNCTIONS =======================
# ====================================================

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

PORTS_MOVED_DB=${PORTSDIR}/MOVED

rm_a_line ()
{
	local pattern dstpath
	pattern=$1
	dstpath=$2
	grep -v -e "^$pattern$" "$dstpath" 2> /dev/null > ${TMPDIR}/rm_a_line || :
	mv "${TMPDIR}/rm_a_line" "$dstpath"
}

add_a_line_if_new ()
{
	local item dstpath
	item=$1
	dstpath=$2
	grep -m 1 "^$item$" "$dstpath" 2> /dev/null > /dev/null || echo $item >> $dstpath
}

record_success ()
{
	local origin clean recurse
	origin=$1
	clean=$2
	recurse=$3
	sed "s|^|^|; s|$|$|" "${DBDIR}/failed.list" > ${TMPDIR}/record_success.grep_failed.list.tmp
	if [ `grep -m 1 -f "${TMPDIR}/record_success.grep_failed.list.tmp" "${DBDIR}/requires/$origin/requires" | wc -l` -eq 0 ]
	then
		add_a_line_if_new "$origin" "${DBDIR}/success.list"
		rm_a_line "$origin" "${DBDIR}/success_but_dependencies_failed.list"
	else
		rm_a_line "$origin" "${DBDIR}/success.list"
		add_a_line_if_new "$origin" "${DBDIR}/success_but_dependencies_failed.list"
	fi
	rm_a_line "$origin" "${DBDIR}/failed.list"
	if [ -z "$clean" -o "@$clean" = @noclean ]
	then
		env ${MAKE_ENVS} make clean ${MAKE_ARGS}
	fi
	_RECORD_SUCCESS_REFRESH_TODO=
	if [ "@$recurse" = @recurse ]
	then
		if [ -f "${DBDIR}/requires/$origin/dependents" ]
		then
			echo "(Inspecting dependents of $origin)"
			add_lines_if_new reinst_todo.list advance < ${DBDIR}/requires/$origin/dependents
			_RECORD_SUCCESS_REFRESH_TODO=yes
		else
			echo "(No dependents for $origin)"
		fi
	fi
}

record_failure ()
{
	local origin noclean
	origin=$1
	noclean=$2
	add_a_line_if_new "$origin" "${DBDIR}/failed.list"
	rm_a_line "$origin" "${DBDIR}/success.list"
	rm_a_line "$origin" "${DBDIR}/success_but_dependencies_failed.list"
	if [ -z "$noclean" -o "@$noclean" = @clean ]
	then
		echo "*** Trying to clean the failed build... (Ignore failures)"
		env ${MAKE_ENVS} make clean ${MAKE_ARGS} || :
	fi
	echo "*** Skipping this port and proceeding to the next one forcedly..."
	echo
}

add_a_line_to_files_if_new ()
{
	local item
	item=$1
	while read filepath
	do
		add_a_line_if_new "$item" "$filepath"
	done
}

rm_matched_lines_from_files ()
{
	local pattern
	pattern=$1
	while read filepath
	do
		rm_a_line "$pattern" "$filepath"
	done
}

add_lines_if_new ()
{
	local filepath origin advance
	filepath=$1
	advance=$2
	while read origin
	do
		grep -m 1 "^$origin$" "$filepath" 2> /dev/null > /dev/null || echo $origin
	done > ${TMPDIR}/add_lines_if_new
	if [ "@$advance" = @advance ]
	then
		mv "$filepath" ${TMPDIR}/add_lines_if_new.bak
		cat "${TMPDIR}/add_lines_if_new" "${TMPDIR}/add_lines_if_new.bak" > $filepath
	else
		cat "${TMPDIR}/add_lines_if_new" >> $filepath
	fi
}

register_globs ()
{
	local globlist listpath mode dirpath origin
	globlist=$1
	listpath=$2
	mode=$3
	dirpath=`dirname "$listpath"`
	for glob in `echo $globlist | sed 's/,/ /g'`
	do
		if expr "@$glob" : '^@[^/][^/]*/[^/][^/]*$' > /dev/null 2> /dev/null
		then
			[ -d "${PORTSDIR}/$glob" ] && { echo $glob; continue; }
		fi
		origin=`ports_glob "$glob"`
		[ "$origin" ] || \
		{
			echo "ERROR: Invalid ports/package glob [$glob]" >&2
			exit 1;
		}
		echo $origin
	done | while read origin
	do
		[ -d "$dirpath" ] || mkdir -p "$dirpath"
		if [ "@$mode" = @remove ]
		then
			rm_a_line "$origin" "$listpath"
		elif grep -m 1 -e "^$origin$" "$listpath" > /dev/null 2> /dev/null
		then
			:
		else
			echo $origin >> "$listpath"
		fi
	done
}

build_conflist_target_list ()
{
	local section
	section=$1
	if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_${section}" ]
	then
		echo "-- ${section}_*"
		set | grep -e "^_CONF_${section}_" | cut -d = -f 1 | while read var
		do
			eval glob_pattern=\${$var}
			pkg_info -qo "$glob_pattern" 2> /dev/null || :
			ports_glob "$glob_pattern" 2> /dev/null || :
		done > ${DBDIR}/${section}_PORTS.conflist
		touch "${DBDIR}/COMPLETE_REFLECTCONF_${section}"
	fi
}

build_conflist_target_val_pair ()
{
	local section tag_target tag_val
	section=$1
	tag_target=$2
	tag_val=$3
	if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_${section}" ]
	then
		echo "-- ${section}_*"
		set | grep -e "^_CONF_${section}_${tag_target}_" | cut -d = -f 1 | while read var
		do
			eval glob_pattern=\${$var}
			eval val=\$\{`echo $var | sed "s/^_CONF_${section}_${tag_target}_/_CONF_${section}_${tag_val}_/"`\}
			pkg_info -qo "$glob_pattern" > ${TMPDIR}/origins.tmp 2> /dev/null || :
			ports_glob "$glob_pattern" >> ${TMPDIR}/origins.tmp 2> /dev/null || :
			sort "${TMPDIR}/origins.tmp" | uniq | while read origin
			do
				path=${DBDIR}/requires/$origin
				[ -d "$path" ] || continue
				echo $val > $path/${section}.conflist
			done
		done
		touch "${DBDIR}/COMPLETE_REFLECTCONF_${section}"
	fi
}

convert_origin_if_moved ()
{
	local origin_src recursedb_in recursedb iline_db date_moved why_moved
	# input/output origin
	origin_src=$1
	recursedb_in=$2
	recursedb=${recursedb_in:-${PORTS_MOVED_DB}}
	[ -d "${PORTSDIR}/$origin" ] && return
	add_a_line_if_new "$origin" "${DBDIR}/moved_or_lost.list"
	grep -n -m 1 -e "^$origin|" "$recursedb" 2> /dev/null > ${TMPDIR}/moved.info || :
	if [ `cat "${TMPDIR}/moved.info" | wc -l` -eq 0 ]
	then
		if [ "$recursedb_in" ]
		then
			echo "${DEPTH_INDEX}  ===> Disappeared port (MOVED broken?)"
		else
			echo "${DEPTH_INDEX}  ===> Nonexistent port (your original?)"
		fi
		[ "$origin_src" ] && add_a_line_if_new "$origin_src" "${DBDIR}/moved_or_lost.list"
		return 1
	else
		iline_db=`cut -d : -f 1 "${TMPDIR}/moved.info"`
		sed 1,${iline_db}d "${PORTS_MOVED_DB}" > ${TMPDIR}/MOVED.DB
		origin=`sed -E 's/^[0-9]+://' "${TMPDIR}/moved.info" | cut -d '|' -f 2 || :`
		date_moved=`cut -d '|' -f 3 "${TMPDIR}/moved.info" || :`
		why_moved=`cut -d '|' -f 4 "${TMPDIR}/moved.info" || :`
		if [ "$origin" ]
		then
			echo "${DEPTH_INDEX}  ===> Moved to $origin at $date_moved because \"$why_moved\""
			convert_origin_if_moved "$origin_src" "${TMPDIR}/MOVED.DB" || return 1
		else
			echo "${DEPTH_INDEX}  ===> Deleted at $date_moved because \"$why_moved\""
			[ "$origin_src" ] || return 1
			origin=$origin_src
			echo "${DEPTH_INDEX}  ===> Going back to the original port $origin_src"
			convert_origin_if_moved || return 1
		fi
	fi
}

inspect_dependencies ()
{
	local origin origin_orig pkg origin_id origin_src port_exists origin_dependency DEPTH_INDEX_orig nlines iline
	origin=$1
	origin_orig=$origin
	DEPTH_INDEX_orig=${DEPTH_INDEX}
	DEPTH_INDEX="${DEPTH_INDEX}--"
	echo "${DEPTH_INDEX} $origin"
	origin_id=`echo $origin | tr / _`
	origin_src=
	if [ `grep -m 1 "^$origin$" "${DBDIR}/REPLACE.from.conflist" | wc -l` -eq 1 ]
	then
		origin_src=$origin
		origin=`echo $origin_src | sed -f "${DBDIR}/REPLACE.replace_pattern.conflist"`
		add_a_line_if_new "$origin_src" "${DBDIR}/moved_or_lost.list"
		if [ "$origin" ]
		then
			add_a_line_if_new "$origin" "${DBDIR}/replaced_target.inspected.list"
			echo "${DEPTH_INDEX}  ===> Replaced with $origin by user configuration"
		else
			echo "${DEPTH_INDEX}  ===> Deleted by user configuration"
		fi
	fi
	if [ "$origin" ]
	then
		if convert_origin_if_moved "$origin_src" ''
		then
			port_exists=yes
		else
			port_exists=no
		fi
		if [ $port_exists = yes ]
		then
			cd "${PORTSDIR}/$origin"
			make config-conditional
			[ -d "${DBDIR}/requires/$origin/status" ] || mkdir -p "${DBDIR}/requires/$origin/status"
			pkg=`pkg_info -qO "$origin"`
			[ "$pkg" ] && echo $pkg > "${DBDIR}/requires/$origin/installed_version"
			make all-depends-list | sed "s|^${PORTSDIR}/||" > "${DBDIR}/requires/$origin/requires" || :
			grep -f "${DBDIR}/REPLACE.grep_from_pattern.conflist" "${DBDIR}/requires/$origin/requires" | grep -v -e "^$origin$" > ${TMPDIR}/replaced_target.tmp || :
			if [ `cat "${TMPDIR}/replaced_target.tmp" | wc -l` -gt 0 ]
			then
				mv "${DBDIR}/requires/$origin/requires" "${DBDIR}/requires/$origin/requires.orig"
				sed -f "${DBDIR}/REPLACE.replace_pattern.conflist" "${DBDIR}/requires/$origin/requires.orig" | grep -v '^$' > ${DBDIR}/requires/$origin/requires || :
				add_lines_if_new "${DBDIR}/replaced_target.list" < ${TMPDIR}/replaced_target.tmp
				grep -v -f "${DBDIR}/installed_ports.grep_pattern" "${DBDIR}/requires/$origin/requires" > ${TMPDIR}/missing.$origin_id || :
			else
				make missing > ${TMPDIR}/missing.$origin_id
			fi
			nlines=`cat "${TMPDIR}/missing.$origin_id" | wc -l`
			iline=1
			while [ $iline -le $nlines ]
			do
				origin_dependency=`sed -n ${iline}p "${TMPDIR}/missing.$origin_id"`
				iline=$(($iline+1))
				grep -m 1 -e "^$origin_dependency$" "${DBDIR}/target.inspected.list" > /dev/null 2> /dev/null && continue
				inspect_dependencies "$origin_dependency"
			done
			rm "${TMPDIR}/missing.$origin_id"
			make fetch-urlall-list | sed -E 's|.*/([^/]+)$|\1|' | sort | uniq >> ${DBDIR}/distfiles.list
		fi
	fi
	[ "$origin_orig" = "$origin" ] || echo "s|^$origin_orig$|$origin|" >> ${DBDIR}/REPLACE.complete_replace_pattern.tmp
	add_a_line_if_new "$origin" "${DBDIR}/target.inspected.list"
	rm_a_line "$origin" "${DBDIR}/target_ports.remain"
	if [ "$origin_src" ]
	then
		add_a_line_if_new "$origin_src" "${DBDIR}/target.inspected.list"
		rm_a_line "$origin_src" "${DBDIR}/target_ports.remain"
	fi
	echo "${DEPTH_INDEX}  ===> ok"
	DEPTH_INDEX=${DEPTH_INDEX_orig}
}

cmt_fail_reinst ()
{
	local origin
	origin=$1
	echo "*** Giving up for this port $origin and proceeding to the next one forcedly..."
	echo
}

timestamp ()
{
	echo "`env LANG= date` (GMT date: `env LANG= date -u +%Y%m%d`)"
}

warn_update_ports ()
{
	[ ${_STATUS_DB_OBSOLETE} = no ] && return
	echo "WARN: The Ports tree was updated after construction of the temporal databese for ${APPNAME}." >&2
	echo "      You should consider executing " >&2
	echo "               ${APPNAME} clean" >&2
	echo "     to reset the temporal databese unless you have special purposes." >&2
}

linear_list ()
{
	echo $* | sed -E 's/^ +//; s/ +$//; s/ +/, /; s/, ([^ ]+)$/ and \1/'
}

chk_privilege ()
{
	[ `id -u` -eq 0 ] || { echo "ERROR: Only the superuser can execute this command." >&2; exit 1; }
}

combine_lists ()
{
	local src_conf src_opt dst
	src_conf=$1
	src_opt=$2
	dst=$3
	cat "${DBDIR}/$src_conf" "${DBDIR}/$src_opt" 2> /dev/null | sort | uniq > ${DBDIR}/$dst || :
}

# ==================================================
# ==================== MAIN ========================
# ==================================================

# Title
credit
echo
echo " Don't hesitate to abort by CTRL+C anytime you feel the system is heavy to use"
echo "because you can restart the operation from the aborted point quickly."
echo
echo "The current time is `timestamp`"
echo

# Creation of temporary work directories
terminate_process ()
{
}
{ until TMPDIR=`mktemp -dq /tmp/${APPNAME}.XXXXXX 2> /dev/null` ; do : ; done ; }
trap 'errno=$?; warn_update_ports; terminate_process; rm -r'`[ \`uname -s\` = FreeBSD ] && echo -n P`' "${TMPDIR}" 2> /dev/null; [ $errno -gt 0 -a $errno -ne 130 ] && echo "(Error exit by $errno)" >&2; exit $errno' 0 1 2 3 9 15 17 18

# Check of conflictiong option
[ -n "$target_ports" -a -d "${DBDIR}" ] && echo "WARN: -t option is specified but ignored by following the previous settings." >&2
[ "$load_pkgtoolsconf" != no -a -d "${DBDIR}/COMPLETE_IMPORT_PKGTOOLS_CONF" ] && echo "WARN: -p or -P option is specified but ignored by following the previous settings." >&2

# Check whether the temporal database is newer than the ports tree
_STATUS_DB_OBSOLETE=no
ls /usr/ports/*.db | while read ref
do
	[ "$ref" -nt "${DBDIR}" ] && { _STATUS_DB_OBSOLETE=yes; break; }
	:
done

# Creation of temporal database directory
[ -d "${DBDIR}" ] || mkdir -p "${DBDIR}"

# Taboo list given by options
register_globs "$taboo_ports" "${DBDIR}/taboo.list"


# ------- Commands -------

# Special modes
case ${command:-do} in
clean)
	chk_privilege
	echo "Starting to clean up the temporal database..."
	rm -rf "${DBDIR}"
	echo "Done"
	exit
	;;
ok)
	chk_privilege
	warn_update_ports
	opr=$1
	shift
	case $opr in
	add)
		register_globs "$*" "${DBDIR}/manually_done.list"
		echo "`linear_list $*` is/are registered to the list of manually resolved ports"
		;;
	del)
		register_globs "$*" "${DBDIR}/manually_done.list" remove
		echo "`linear_list $*` is/are deregistered from the list of manually resolved ports"
		;;
	esac
	echo "Now the following ports have been manually resolved:"
	[ -r "${DBDIR}/manually_done.list" ] && cat  "${DBDIR}/manually_done.list"
	exit
	;;
taboo)
	chk_privilege
	warn_update_ports
	opr=$1
	shift
	case $opr in
	add)
		register_globs "$*" "${DBDIR}/taboo.list"
		echo "`linear_list $*` is/are registered to the list of ports to be ignored."
		;;
	del)
		register_globs "$*" "${DBDIR}/taboo.list" remove
		echo "`linear_list $*` is/are deregistered from the list of ports to be ignored."
		;;
	esac
	combine_lists TABOO_PORTS.conflist taboo.list taboo.all.list
	echo "Now the following ports are registered to be ignored:"
	cat "${DBDIR}/taboo.all.list"
	exit
	;;
save)
	chk_privilege
	[ -d "${DBDIR}" ] || { echo "ERROR: Database is not found." >&2; exit 1; }
	savedir=$2
	[ "$savedir" ] || { echo "ERROR: Directory to save the temporal database archive is not specified." >&2; exit 1; }
	[ -d "$savedir" ] || { echo "ERROR: Directory [$savedir] is not found." >&2; exit 1; }
	srcdir=`dirname "${DBDIR}"`
	srcnode=`basename "${DBDIR}"`
	savefile=`realpath "$savedir"`/${APPNAME}_`date +%S%m%d_%H%M%S`.tar.gz
	echo "Starting to save the temporal database as [$savefile]..."
	( cd "$srcdir" && tar czf "$savefile" "$srcnode" )
	echo "Done"
	exit
	;;
load)
	chk_privilege
	loadfile=$2
	[ "$loadfile" ] || { echo "ERROR: Database archive is not specified." >&2; exit 1; }
	[ -f "$loadfile" ] || { echo "ERROR: Database archive is not found." >&2; exit 1; }
	loadfile=`realpath "$loadfile"`
	echo "Starting to load the temporal database..."
	rm -rf "${DBDIR}"
	srcdir=`dirname "${DBDIR}"`
	srcnode=`basename "${DBDIR}"`
	[ -d "$srcdir" ] || mkdir -p "$srcdir"
	( cd "$srcdir" && tar xzf "$loadfile" )
	echo "Done"
	exit
	;;
show)
	warn_update_ports
	case ${1:-todo} in
	todo)
		echo "The following ports are to be reinstalled:"
		list=reinst_todo.list
		;;
	done)
		echo "The following ports have been successfully reinstalled:"
		list=success.list
		;;
	redo)
		echo "The following ports themselves have been successfully reinstalled,"
		echo "but are to be reinstalled again because their dependencies were failed:"
		list=success_but_dependencies_failed.list
		;;
	resolved)
		echo "The following ports have been manually resolved:"
		list=manually_done.list
		;;
	failure)
		echo "The following ports experienced failures and kept to be old:"
		list=failed.list
		;;
	taboo)
		echo "The following ports are registered as taboo:"
		list=taboo.all.list
		;;
	*)
		echo "ERROR: Invalid show argument [$1]" >&2
		exit 1
		;;
	esac
	[ -r "${DBDIR}/$list" ] && cat "${DBDIR}/$list"
	exit
	;;
redo)
	chk_privilege
	warn_update_ports
	touch "${DBDIR}/MODE_REDO"
	rm -f "${DBDIR}/COMPLETE_CLEANUP_REINST_STATUS"
	echo "[REDO mode]"
	echo "The temporal database will be refreshed so as to reinstall the all failed ports and their dependents."
	;;
prepare)
	chk_privilege
	warn_update_ports
	;;
do)
	chk_privilege
	;;
*)
	echo "ERROR: Invalid command [$command]" >&2
	exit 1
	;;
esac

# ------- Load from pkgtools.conf -------

if [ ! -e "${DBDIR}/COMPLETE_IMPORT_PKGTOOLS_CONF" ]
then
	if [ $load_pkgtoolsconf != no -a `which portupgrade | wc -l` -eq 0 ]
	then
		echo "WARN: pkgtools.conf is ignored because portupgrade is not installed" >&2
		load_pkgtoolsconf=no
	fi
	
	if [ $load_pkgtoolsconf != no ]
	then
		echo "-- Starting to parse pkgtools.conf at `timestamp`" >&2
		PORTUPGRADE=`which portupgrade`
		istart=`grep -m 1 -n -e '^def init_global$' "${PORTUPGRADE}" | cut -d : -f 1` || :
		[ "$istart" ] || { echo "ERROR: The current installed version of portupgrade is unsupported." >&2; }
		sed 1,$(($istart-1))d "${PORTUPGRADE}" > ${TMPDIR}/portupgrade.0
		iend=`grep -m 1 -n -e '^end$' ${TMPDIR}/portupgrade.0 | cut -d : -f 1`
		sed -n 1,${iend}p ${TMPDIR}/portupgrade.0 > ${TMPDIR}/portupgrade.init_global
		ruby > ${DBDIR}/pkgtools.conf.converted << eof
MYNAME = 'portupgrade'
require 'pkgtools'
`cat "${TMPDIR}"/portupgrade.init_global`
init_global
init_pkgtools_global
load_config
alt_moved = config_value(:ALT_MOVED)
hold_pkgs = config_value(:HOLD_PKGS)
ignore_moved = config_value(:IGNORE_MOVED)
alt_pkgdep = config_value(:ALT_PKGDEP)
make_args = config_value(:MAKE_ARGS)
make_env = config_value(:MAKE_ENV)
beforebuild = config_value(:BEFOREBUILD)
beforedeinstall = config_value(:BEFOREDEINSTALL)
afterinstall = config_value(:AFTERINSTALL)

printf("ENV_PORTSDIR=%s\n", ENV['PORTSDIR'])

i = 0
alt_moved.each do |alt|
        printf("ALT_MOVED_pkgtoolsconf_%d_='%s'\n", i, alt)
        i = i + 1
end

i = 0
hold_pkgs.each do |alt|
        printf("HOLD_pkgtoolsconf_%d_='%s'\n", i, alt)
        i = i + 1
end

i = 0
ignore_moved.each do |alt|
        printf("HOLD_pkgtoolsconf_%d_='%s'\n", i, alt)
        i = i + 1
end

i = 0
alt_pkgdep.each do |pat, alt|
        printf("REPLACE_FROM_pkgtoolsconf_%d_='%s'\n", i, pat)
        printf("REPLACE_TO_pkgtoolsconf_%d_='%s'\n", i, alt)
        i = i + 1
end

i = 0
make_args.each do |target, definition|
        printf("MARG_TARGET_pkgtoolsconf_%d_='%s'\n", i, target)
        printf("MARG_DEF_pkgtoolsconf_%d_='%s'\n", i, definition)
        i = i + 1
end

i = 0
make_env.each do |target, definition|
        printf("MENV_TARGET_pkgtoolsconf_%d_='%s'\n", i, target)
        printf("MENV_DEF_pkgtoolsconf_%d_='%s'\n", i, definition)
        i = i + 1
end

i = 0
beforebuild.each do |target, command|
        printf("BEFOREBUILD_TARGET_pkgtoolsconf_%d_='%s'\n", i, target)
        printf("BEFOREBUILD_COMMAND_%d_='%s'\n", i, command)
        i = i + 1
end

i = 0
beforedeinstall.each do |target, command|
        printf("BEFOREDEINSTALL_TARGET_pkgtoolsconf_%d_='%s'\n", i, target)
        printf("BEFOREDEINSTALL_COMMAND_%d_='%s'\n", i, command)
        i = i + 1
end

i = 0
afterinstall.each do |target, command|
        printf("AFTERINSTALL_TARGET_pkgtoolsconf_%d_='%s'\n", i, target)
        printf("AFTERINSTALL_COMMAND_%d_='%s'\n", i, command)
        i = i + 1
end
eof
		if [ $load_pkgtoolsconf = default ]
		then
			cat "${DBDIR}"/pkgtools.conf.converted "${CONFFILE}" > ${DBDIR}/setup.conf 
		elif [ $load_pkgtoolsconf = override ]
		then
			cat "${CONFFILE}" "${DBDIR}"/pkgtools.conf.converted > ${DBDIR}/setup.conf
		fi
	else
		ln -s "${CONFFILE}" "${DBDIR}"/setup.conf
	fi
	echo "===> ok"
	echo
	touch "${DBDIR}/COMPLETE_IMPORT_PKGTOOLS_CONF"
fi

# ------- Configurations -------

if [ ! -e "${DBDIR}/COMPLETE_PARSE_OPTION_TARGET_PORTS" ]
then
	register_globs "$target_ports" "${TMPDIR}/target_ports.specified"
	if [ -f "${TMPDIR}/target_ports.specified" ]
	then
		if [ `cat "${TMPDIR}/target_ports.specified" | wc -l` -ge 1 ]
		then
			echo "Starting to clean up the temporal database at `timestamp`"
			rm -rf "${DBDIR}"
			mkdir -p "${DBDIR}"
			mv "${TMPDIR}/target_ports.specified" "${DBDIR}/"
			echo
		fi
	fi
	touch "${DBDIR}/COMPLETE_PARSE_OPTION_TARGET_PORTS"
fi

# Parse configuration file
if [ ! -e "${DBDIR}/COMPLETE_PARSE_CONF" ]
then
	echo "Start to parse configuration file [${APPNAME}.conf] at `timestamp`"
	(
		set -e
		_CONFVARS='ENV ALT_MOVED HOLD TABOO REPLACE_FROM REPLACE_TO MARG_TARGET MARG_DEF MENV_TARGET MENV_DEF BEFOREBUILD BEFOREDEINSTALL AFTERINSTALL'
		for item in ${_CONFVARS}
		do
			set | grep -e "^${item}_" | cut -d = -f 1 | sed 's/^/unset /'
		done > ${TMPDIR}/unsetvars.sh
		. "${TMPDIR}"/unsetvars.sh
		. "${DBDIR}"/setup.conf
		for item in ${_CONFVARS}
		do
			set | grep -e "^${item}_" | sed 's/^/_CONF_/'
		done > ${DBDIR}/confvars.sh
	) || { echo "ERROR: Invalid syntax in [${CONFFILE}]" >&2; exit 1; }
	touch "${DBDIR}/COMPLETE_PARSE_CONF"
fi
. "${DBDIR}/confvars.sh"

if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_1" ]
then
	echo "Starting to reflect settings of replacement defined in the configuration file at `timestamp`"
	echo PORTSDIR=${_CONF_ENV_PORTSDIR:-${PORTSDIR}} > "${DBDIR}/setenv.sh"
	echo DISTDIR=${_CONF_ENV_DISTDIR:-${DISTDIR:-${PORTSDIR}/distfiles}} >> "${DBDIR}/setenv.sh"
	
	if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_ALT_MOVED" ]
	then
		echo "-- ALT_MOVED_*"
		cp -p "${PORTS_MOVED_DB}" "${TMPDIR}/MOVED.conflist"
		set | grep -e '^_CONF_ALT_MOVED_' | cut -d = -f 1 | while read var
		do
			eval movedsb_path=\${$var}
			cat "$movedsb_path" >> ${DBDIR}/MOVED.conflist
		done
		touch "${DBDIR}/COMPLETE_REFLECTCONF_ALT_MOVED"
	fi
	[ "${DBDIR}/MOVED.conflist" -nt "${PORTS_MOVED_DB}" ] && PORTS_MOVED_DB=${DBDIR}/MOVED.conflist
	
	build_conflist_target_list HOLD
	build_conflist_target_list TABOO
	combine_lists TABOO_PORTS.conflist taboo.list taboo.all.list
	
	if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_REPLACE" ]
	then
		echo "-- REPLACE_*"
		cp /dev/null "${TMPDIR}/REPLACE.from.conflist.tmp"
		cp /dev/null "${TMPDIR}/REPLACE.to.conflist.tmp"
		cp /dev/null "${TMPDIR}/REPLACE.grep_from_pattern.conflist.tmp"
		cp /dev/null "${TMPDIR}/REPLACE.grep_to_pattern.conflist.tmp"
		cp /dev/null "${DBDIR}/REPLACE.replace_pattern.conflist.tmp"
		set | grep -e '^_CONF_REPLACE_FROM_' | cut -d = -f 1 | while read var
		do
			eval glob_pattern=\${$var}
			eval to=\${`echo $var | sed 's/^_CONF_REPLACE_FROM_/_CONF_REPLACE_TO_/'`}
			pkg_info -qo "$glob_pattern" > ${TMPDIR}/origins.tmp 2> /dev/null || :
			ports_glob "$glob_pattern" >> ${TMPDIR}/origins.tmp 2> /dev/null || :
			grep -v -e '^$' "${TMPDIR}/origins.tmp" | sort | uniq > ${TMPDIR}/origins.2.tmp || :
			cat "${TMPDIR}/origins.2.tmp" >> ${TMPDIR}/REPLACE.from.conflist.tmp
			sed "s|^|^|; s|$|$|" "${TMPDIR}/origins.2.tmp" >> ${TMPDIR}/REPLACE.grep_from_pattern.conflist.tmp
			if [ -z "$to" -o "$to" = delete ]
			then
				to=
			else
				[ -d "${PORTSDIR}/$to" ] || echo "WARN: replacement port [$to] is obsolete" >&2
				echo "$to" >> ${TMPDIR}/REPLACE.to.conflist.tmp
				echo "^$to$" >> ${TMPDIR}/REPLACE.grep_to_pattern.conflist.tmp
			fi
			sed "s|^|s:^|; s|$|$:$to:|" "${TMPDIR}/origins.2.tmp" >> ${DBDIR}/REPLACE.replace_pattern.conflist.tmp
		done
		sort "${TMPDIR}/REPLACE.from.conflist.tmp" | uniq > ${DBDIR}/REPLACE.from.conflist
		sort "${TMPDIR}/REPLACE.to.conflist.tmp" | uniq > ${DBDIR}/REPLACE.to.conflist
		sort "${TMPDIR}/REPLACE.grep_from_pattern.conflist.tmp" | uniq > ${DBDIR}/REPLACE.grep_from_pattern.conflist
		sort "${TMPDIR}/REPLACE.grep_to_pattern.conflist.tmp" | uniq > ${DBDIR}/REPLACE.grep_to_pattern.conflist
		sort "${DBDIR}/REPLACE.replace_pattern.conflist.tmp" | uniq > ${DBDIR}/REPLACE.replace_pattern.conflist
		touch "${DBDIR}/COMPLETE_REFLECTCONF_REPLACE"
	fi
	touch "${DBDIR}/COMPLETE_REFLECTCONF_1"
fi
. "${DBDIR}/setenv.sh"

# ------- Database construction -------

terminate_process ()
{
	echo
	echo "INFO: Aborted at `timestamp`"
	echo
	echo " You can restart this process from the aborted/terminated point to the end by"
	echo "executing without options or arguments as:"
	echo "  ${APPNAME}"
	echo " Instead, if you only want to construct the temporal database so as to stop"
	echo "before the actual reinstallation, execute as:"
	echo "  ${APPNAME} prepare"
	terminate_process ()
	{
	}
}

# Target ports
if [ ! -e "${DBDIR}/COMPLETE_TARGETS" ]
then
	echo "Starting to collect installed packages at `timestamp`"
	pkg_info -aoq 2> /dev/null > ${DBDIR}/installed_ports
	if [ -f "${DBDIR}/target_ports.specified" ]
	then
		echo "Starting to parse targets.."
		while read origin
		do
			echo "-- $origin"
			echo $origin >> ${TMPDIR}/target_ports
			pkg=`pkg_info -qO "$origin" 2> /dev/null || :`
			[ "$pkg" ] && pkg_info -qR "$pkg" 2> /dev/null | grep -v '^$' | while read pkg_dependent
			do
				origin_dependent=`pkg_info -qo "$pkg_dependent" 2> /dev/null || :`
				echo $origin_dependent >> ${TMPDIR}/target_ports
			done
		done < ${DBDIR}/target_ports.specified
		sort "${TMPDIR}/target_ports" | uniq > ${DBDIR}/target_ports
	else
		ln -f "${DBDIR}/installed_ports" ${DBDIR}/target_ports
	fi
	sed 's/^/^/; s/$/$/' "${DBDIR}/installed_ports" > ${DBDIR}/installed_ports.grep_pattern
	touch "${DBDIR}/COMPLETE_TARGETS"
	echo
fi

# Inspection of all dependencies
if [ ! -e "${DBDIR}/COMPLETE_COLLECED_ALL_DEPENDENCIES" ]
then
	echo "Starting to inspect dependencies of installed packages at `timestamp`"
	[ -d "${DBDIR}/requires" ] || mkdir -p "${DBDIR}/requires"
	touch "${DBDIR}/moved_or_lost.list"
	touch "${DBDIR}/target.inspected.list"
	if [ -f "${DBDIR}/target_ports.remain" ]
	then
		echo "INFO: Restarting from the previously aborted point"
	else
		cp -p "${DBDIR}/target_ports" "${DBDIR}/target_ports.remain"
	fi
	cp "${DBDIR}/target_ports.remain" "${TMPDIR}/target_ports"
	DEPTH_INDEX='--'
	nlines=`cat "${TMPDIR}/target_ports" | wc -l`
	iline=1
	while [ $iline -le $nlines ]
	do
		origin=`sed -n ${iline}p "${TMPDIR}/target_ports"`
		iline=$(($iline+1))
		inspect_dependencies "$origin"
		continue
	done
	touch "${DBDIR}/REPLACE.complete_replace_pattern.tmp"
	sort "${DBDIR}/REPLACE.complete_replace_pattern.tmp" | uniq > ${DBDIR}/REPLACE.complete_replace_pattern
	touch "${DBDIR}/COMPLETE_COLLECED_ALL_DEPENDENCIES"
	echo
fi

# Inspection of all required distfiles
if [ ! -e "${DBDIR}/COMPLETE_DISTFILES_LIST" ]
then
	echo "Starting to summarize distfiles list at `timestamp`"
	sort "${DBDIR}/distfiles.list" 2> /dev/null | uniq | sed 's|\.|\\.|g; s|^|/|; s|$|$|' > ${DBDIR}/distfiles.grep.pattern || :
	touch "${DBDIR}/COMPLETE_DISTFILES_LIST"
	echo
fi

# Convert requires-lists to actual ones
if [ ! -e "${DBDIR}/COMPLETE_CONVERT_REQUIRES_LIST" ]
then
	echo "Starting convert requires-lists to actual ones at `timestamp`"
	if [ -f "${DBDIR}/convert_requires_lists.remain" ]
	then
		echo "INFO: Restarting from the previously aborted point"
	else
		find "${DBDIR}/requires" -depth 2 -type d > ${DBDIR}/convert_requires_lists.remain
	fi
	cp "${DBDIR}/convert_requires_lists.remain" "${TMPDIR}/convert_requires_lists"
	while read dbpath
	do
		portname=`basename "$dbpath"`
		catpath=`dirname "$dbpath"`
		catname=`basename "$catpath"`
		origin=$catname/$portname
		sed -f "${DBDIR}/REPLACE.complete_replace_pattern" "$dbpath/requires" | grep -v '^$' | sort | uniq > $dbpath/requires.new
		mv "$dbpath/requires.new" "$dbpath/requires"
		sed -i '' 1d "${DBDIR}/convert_requires_lists.remain"
	done < ${TMPDIR}/convert_requires_lists
	touch "${DBDIR}/COMPLETE_CONVERT_REQUIRES_LIST"
	echo
fi

# Inspection of dependents
if [ ! -e "${DBDIR}/COMPLETE_INSPECT_DEPENDENTS" ]
then
	echo "Starting inspection of dependents at `timestamp`"
	if [ -f "${DBDIR}/inspect_dependent.remain" ]
	then
		echo "INFO: Restarting from the previously aborted point"
	else
		find "${DBDIR}/requires" -depth 2 -type d > ${DBDIR}/inspect_dependent.remain
	fi
	cp "${DBDIR}/inspect_dependent.remain" "${TMPDIR}/inspect_dependent"
	while read dbpath
	do
		portname=`basename "$dbpath"`
		catpath=`dirname "$dbpath"`
		catname=`basename "$catpath"`
		origin=$catname/$portname
		sed "s|^|${DBDIR}/requires/|; s|$|/dependents|" "$dbpath/requires" | add_a_line_to_files_if_new "$origin"
		sed -i '' 1d "${DBDIR}/inspect_dependent.remain"
	done < ${TMPDIR}/inspect_dependent
	touch "${DBDIR}/COMPLETE_INSPECT_DEPENDENTS"
	echo
fi

# Copying dependencies for preparation
if [ ! -e "${DBDIR}/COMPLETE_COPY_DEPENDENCY_TMPFILES" ]
then
	echo "Starting preparation for order of dependencies at `timestamp`"
	find "${DBDIR}/requires" -depth 3 -name requires -exec cp -p {} {}.remained \;
	touch "${DBDIR}/COMPLETE_COPY_DEPENDENCY_TMPFILES"
	echo
fi

# Clean up of reinstallation status for preparation
if [ -e "${DBDIR}/MODE_REDO" -a ! -e "${DBDIR}/COMPLETE_CLEANUP_REINST_STATUS" ]
then
	echo "Starting preparation for order of dependencies at `timestamp`"
	find "${DBDIR}/requires" -depth 3 -type d -name status -exec rm -rf {} \; -exec mkdir {} \;
	touch "${DBDIR}/COMPLETE_CLEANUP_REINST_STATUS"
	echo
fi

# Order the ports considering dependencies
if [ ! -e "${DBDIR}/COMPLETE_ORDERED_ALL_DEPENDENCIES" ]
then
	echo "Starting order of dependencies at `timestamp`"
	[ -f "${DBDIR}/reinst_order.list" ] && echo "INFO: Restarting from the previously aborted point"
	touch "${DBDIR}/reinst_order.list"
	cd "${DBDIR}/requires"
	while :
	do
		find . -depth 3 -name requires.remained -empty | \
			sed 's|\./||; s|/requires\.remained$||' > ${TMPDIR}/targets
		[ `cat "${TMPDIR}/targets" | wc -l` -eq 0 ] && break
		while read origin
		do
			echo "-- $origin"
			grep "^$origin$" "${DBDIR}/reinst_order.list" 2> /dev/null > /dev/null || echo $origin >> ${DBDIR}/reinst_order.list
			find "${DBDIR}/requires" -depth 3 -name requires.remained | rm_matched_lines_from_files "$origin"
			rm "${DBDIR}/requires/$origin/requires.remained"
		done < ${TMPDIR}/targets
	done
	touch "${DBDIR}/reinst_order.list"
	touch "${DBDIR}/COMPLETE_ORDERED_ALL_DEPENDENCIES"
	rm -f "${TMPDIR}/targets"
	echo
fi

# Check of unsatisflied dependencies
if [ ! -e "${DBDIR}/COMPLETE_CHECKED_UNSATISFIED_DEPENDENCIES" ]
then
	echo "Starting to check rmained unsatisfied dependencies at `timestamp`"
	cd "${DBDIR}/requires"
	find . -depth 3 -name requires.remained 2> /dev/null | \
		sed 's|\./||; s|/requires\.remained$||' > ${TMPDIR}/unsatisfied || :
	if [ `cat "${TMPDIR}/unsatisfied" | wc -l` -gt 0 ]
	then
		echo "ERROR: Unsatisfied dependencies are remained" >&2
		cat "${TMPDIR}/unsatisfied"
		echo "*** Aborted by ${APPNAME}"
		echo "The ports tree seems broken. You might have caught an incomplete version."
		echo "You are encouraged to update the ports tree by portsnap(8)."
		echo "Then execute"
		echo " ${APPNAME} clean"
		echo "before retsart."
		terminate_process ()
		{
		}
		exit 1
	fi
	touch "${DBDIR}/COMPLETE_CHECKED_UNSATISFIED_DEPENDENCIES"
	echo
fi

if [ ! -e "${DBDIR}/COMPLETE_REFLECTCONF_2" ]
then
	echo "Starting to reflect settings for each port defined in the configuration file at `timestamp`"
	build_conflist_target_val_pair MARG TARGET DEF
	build_conflist_target_val_pair MENV TARGET DEF
	build_conflist_target_val_pair BEFOREBUILD TARGET COMMAND
	build_conflist_target_val_pair BEFOREDEINSTALL TARGET COMMAND
	build_conflist_target_val_pair AFTERINSTALL TARGET COMMAND
	touch "${DBDIR}/COMPLETE_REFLECTCONF_2"
	echo
fi

# ------- Main operations -------

terminate_process ()
{
	echo
	echo "INFO: Aborted at `timestamp`"
	echo
	echo " You can restart this process from the aborted/terminated point by"
	echo "executing without options or arguments as:"
	echo "  ${APPNAME}"
	terminate_process ()
	{
	}
}

if [ "$command" = prepare ]
then
	echo "Done (skipped reinstallation) at `timestamp`"
	echo
	echo " You can restart this process from the aborted/terminated point by"
	echo "executing without options or arguments as:"
	echo "  ${APPNAME}"
	terminate_process ()
	{
	}
	exit
fi

# Deinstallation of obsolete packages
if [ ! -e "${DBDIR}/COMPLETE_DEINST_OBS_PKGS" ]
then
	echo "Starting deinstallation of obsolete packages at `timestamp`"
	[ -d "${DBDIR}/backup_obsolete" ] || mkdir -p "${DBDIR}/backup_obsolete"
	[ -d "${DBDIR}/status_deinst" ] || mkdir -p "${DBDIR}/status_deinst"
	if [ `cat "${DBDIR}/moved_or_lost.list" | wc -l` -gt 0 ]
	then
		savedir=`pwd`
		if [ -f "${DBDIR}/moved_or_lost.list.remained" ]
		then
			echo "INFO: Restarting from the previously aborted point"
		else
			cp -p "${DBDIR}/moved_or_lost.list" "${DBDIR}/moved_or_lost.list.remained"
		fi
		cp -p "${DBDIR}/moved_or_lost.list.remained" "${TMPDIR}/moved_or_lost.tmp"
		nlines=`cat "${TMPDIR}/moved_or_lost.tmp" | wc -l`
		iline=1
		cd "${DBDIR}/backup_obsolete"
		while [ $iline -le $nlines ]
		do
			origin=`sed -n ${iline}p "${TMPDIR}/moved_or_lost.tmp"`
			iline=$(($iline+1))
			currentpkg=`pkg_info -qO "$origin" 2> /dev/null`
			[ "$currentpkg" ] || continue
			if [ `grep -m 1 -e "^${origin}$" "${DBDIR}/HOLD_PORTS.conflist" 2> /dev/null | wc -l` -gt 0 ]
			then
				echo "-- (Skipping a hold package for port $origin as $currentpkg)"
				continue
			fi
			echo "-- (Creating backup package for $origin as $currentpkg)"
			tag=`echo $origin | sed 's|/|.|'`
			if [ ! -e "${DBDIR}/status_deinst/$tag.backup" ]
			then
				pkg_create -b "$currentpkg" || { echo "*** Continuating forcedly by hoping success..."; continue; }
				touch "${DBDIR}/status_deinst/$tag.backup"
			fi
			pkg_delete -f "$currentpkg" || { echo "*** Continuating forcedly by hoping success..."; continue; }
			rm_a_line "$origin" "${DBDIR}/moved_or_lost.list.remained"
			echo
		done
		cd "$savedir"
	fi
	touch "${DBDIR}/COMPLETE_DEINST_OBS_PKGS"
	echo
fi

# Reinstallation of remained ports
[ -e "${DBDIR}/MODE_REDO" ] && rm -f "${DBDIR}/COMPLETE_REINSTALLATION" "${DBDIR}/COMPLETE_CLEANUP_OBSLETE_DISTFILES" "${DBDIR}/COMPLETE_REBUILD_PKGDB"
if [ ! -e "${DBDIR}/COMPLETE_REINSTALLATION" ]
then
	echo "Starting reinstallation at `timestamp`"
	savedir=`pwd`
	mkdir "${TMPDIR}/backup"
	[ -d "${DBDIR}/backup_failure" ] || mkdir -p "${DBDIR}/backup_failure"
	if [ ! -e "${DBDIR}/MODE_REDO" -a -f "${DBDIR}/reinst_todo.list" ]
	then
		echo "INFO: Restarting from the previously aborted point"
	else
		cp -p "${DBDIR}/reinst_order.list" "${DBDIR}/reinst_todo.list"
	fi
	rm -f "${DBDIR}/MODE_REDO"
	cp -p "${DBDIR}/reinst_todo.list" "${TMPDIR}/reinst_todo.tmp"
	touch "${DBDIR}/in_build"
	touch "${DBDIR}/failed.list"
	touch "${DBDIR}/success_but_dependencies_failed.list"
	touch "${DBDIR}/success.list"
	nlines_tot=$((`cat "${DBDIR}/reinst_order.list" | wc -l`))
	nlines=`cat "${TMPDIR}/reinst_todo.tmp" | wc -l`
	icount=$(($nlines_tot-$nlines))
	iline=1
	while [ $iline -le $nlines ]
	do
		origin=`sed -n ${iline}p "${TMPDIR}/reinst_todo.tmp"`
		iline=$(($iline+1))
		icount=$(($icount+1))
		MAKE_ARGS="FORCE_PKG_REGISTER=yes `[ $avoid_vulner = yes ] || echo DISABLE_VULNERABILITIES=yes` `cat "${DBDIR}/requires/$origin/MAKE_ARGS.conflist" 2> /dev/null || :`"
		MAKE_ENVS=`cat "${DBDIR}/requires/$origin/MAKE_ENVS.conflist" 2> /dev/null || :`
		counter="[$icount/$nlines_tot $(($icount*100/$nlines_tot))%]"
		if grep -m 1 -e "^${origin}$" "${DBDIR}/success.list" 2> /dev/null > /dev/null
		then
			echo "========== $counter (Skipping a already reinstalled package for port $origin at `timestamp`) =========="
			echo
			rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
			continue
		fi
		if grep -m 1 -e "^${origin}$" "${DBDIR}/HOLD_PORTS.conflist" 2> /dev/null > /dev/null
		then
			echo "========== $counter (Skipping a hold package for port $origin at `timestamp`) =========="
			echo
			rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
			continue
		fi
		if grep -m 1 -e "^$origin$" "${DBDIR}/taboo.all.list" 2> /dev/null > /dev/null
		then
			echo "========== $counter (Ignored a taboo port $origin at `timestamp`) =========="
			echo
			rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
			continue
		fi
		if grep -m 1 -e "^$origin$" "${DBDIR}/manually_done.list" 2> /dev/null > /dev/null
		then
			echo "========== $counter (Marking a manually-done port $origin as success at `timestamp`) =========="
			echo
			record_success $origin clean
			rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
			if [ "@${_RECORD_SUCCESS_REFRESH_TODO}" = @yes ]
			then
				cp -p "${DBDIR}/reinst_todo.list" "${TMPDIR}/reinst_todo.tmp"
				nlines=`cat "${TMPDIR}/reinst_todo.tmp" | wc -l`
				iline=1
				_RECORD_SUCCESS_REFRESH_TODO=
			fi
			continue
		fi
		echo "========== $counter Starting a build process for $origin at `timestamp` =========="
		cd "${PORTSDIR}/$origin"
		if [ -e "${DBDIR}/requires/$origin/status/in_build" ]
		then
			echo "(Restarting the previously aborted build process...)"
		else
			touch "${DBDIR}/requires/$origin/status/in_build"
		fi
		if [ -e "${DBDIR}/requires/$origin/BEFOREBUILD.conflist" -a ! -e "${DBDIR}/requires/$origin/status/COMPLETE_BEFOREBUILD" ]
		then
			echo "-- BEFOREBUILD operations (start)"
			sh -e "${DBDIR}/requires/$origin/BEFOREBUILD.conflist" || \
			{
				echo "ERROR: while BEFOREBUILD operations for ${PORTSDIR}/$origin." >&2
				record_failure $origin noclean
				rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
				cmt_fail_reinst "$origin"
				continue
			}
			echo "-- BEFOREBUILD operations (end)"
			touch "${DBDIR}/requires/$origin/status/COMPLETE_BEFOREBUILD"
		fi
		if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE_CLEAN_BEFORE_BUILD" ]
		then
			env ${MAKE_ENVS} make clean ${MAKE_ARGS} || \
			{
				echo "ERROR: Check the permission of directory ${PORTSDIR}/$origin." >&2
				record_failure $origin noclean
				rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
				cmt_fail_reinst "$origin"
				continue
			}
			echo
			touch "${DBDIR}/requires/$origin/status/COMPLETE_CLEAN_BEFORE_BUILD"
		fi
		if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE_FETCH" ]
		then
			if [ ! -e "${DBDIR}/requires/$origin/status/FAILED_FETCH" ]
			then
				if env ${MAKE_ENVS} make checksum ${MAKE_ARGS}
				then
					:
				else
					touch "${DBDIR}/requires/$origin/status/FAILED_FETCH"
				fi
			fi
			if [ -e "${DBDIR}/requires/$origin/status/FAILED_FETCH" ]
			then
				echo "WARN: Refetching distfiles for ${PORTSDIR}/$origin." >&2
				if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE.FAILED_FETCH.DISTCLEAN" ]
				then
					env ${MAKE_ENVS} make distclean NOCLEANDEPENDS=yes ${MAKE_ARGS} || \
					{
						echo "ERROR: Failed in distclean for ${PORTSDIR}/$origin." >&2
						record_failure $origin
						rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
						cmt_fail_reinst "$origin"
						continue
					}
					touch "${DBDIR}/requires/$origin/status/COMPLETE.FAILED_FETCH.DISTCLEAN"
				fi
				if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE.FAILED_FETCH.REFETCH" ]
				then
					env ${MAKE_ENVS} make checksum ${MAKE_ARGS} || \
					{
						echo "ERROR: Failed in fetch for ${PORTSDIR}/$origin." >&2
						record_failure $origin
						rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
						cmt_fail_reinst "$origin"
						continue
					}
					touch "${DBDIR}/requires/$origin/status/COMPLETE.FAILED_FETCH.REFETCH"
				fi
			fi
			touch "${DBDIR}/requires/$origin/status/COMPLETE_FETCH"
		fi
		if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE_BUILD" ]
		then
			if env ${MAKE_ENVS} make ${MAKE_ARGS}
			then
				:
			else
				record_failure $origin
				rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
				cmt_fail_reinst "$origin"
				continue
			fi
			touch "${DBDIR}/requires/$origin/status/COMPLETE_BUILD"
		fi
		if [ -e "${DBDIR}/requires/$origin/status/in_install" ]
		then
			echo "(Restarting the previously aborted installation process...)"
		else
			touch "${DBDIR}/requires/$origin/status/in_install"
		fi
		currentpkg=`pkg_info -qO "$origin" 2> /dev/null || :`
		if [ "$currentpkg" -a ! -e "${DBDIR}/requires/$origin/status/COMPLETE_PKG_BACKUP" ]
		then
			echo "-- (Creating temporal backup package for $origin as $currentpkg)"
			cd "${DBDIR}/backup_failure"
			pkg_create -b "$currentpkg" || \
			{
				rm -f "${DBDIR}/backup_failure/$currentpkg.tbz"
				echo "WARN: Failed to create the backup package, but ignored by hoping success." >&2
			}
			cd "${PORTSDIR}/$origin"
			touch "${DBDIR}/requires/$origin/status/COMPLETE_PKG_BACKUP"
		fi
		if [ "$currentpkg" -a ! -e "${DBDIR}/requires/$origin/status/COMPLETE_BEFOREDEINSTALL" ]
		then
			if [ -e "${DBDIR}/requires/$origin/BEFOREDEINSTALL.conflist" ]
			then
				echo "-- BEFOREDEINSTALL operations (start)"
				sh -e "${DBDIR}/requires/$origin/BEFOREDEINSTALL.conflist" || \
				{
					echo "ERROR: while BEFOREDEINSTALL operations for ${PORTSDIR}/$origin." >&2
					record_failure $origin noclean
					rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
					cmt_fail_reinst "$origin"
					continue
				}
				echo "-- BEFOREDEINSTALL operations (end)"
			fi
			touch "${DBDIR}/requires/$origin/status/COMPLETE_BEFOREDEINSTALL"
		fi
		if [ "$currentpkg" -a ! -e "${DBDIR}/requires/$origin/status/COMPLETE_DEINSTALL" ]
		then
			env ${MAKE_ENVS} make deinstall ${MAKE_ARGS} || \
			{
				echo "WARN: Failed to deinstall $currentpkg." >&2
			}
			touch "${DBDIR}/requires/$origin/status/COMPLETE_DEINSTALL"
		fi
		if [ ! -e "${DBDIR}/requires/$origin/status/COMPLETE_INSTALL" ]
		then
			if [ -e "${DBDIR}/requires/$origin/installed_version" ]
			then
				insttarget=reinstall
			else
				insttarget=install
			fi
			if [ ! -e "${DBDIR}/requires/$origin/status/FAILED_INSTALL" ]
			then
				if env ${MAKE_ENVS} make $insttarget clean ${MAKE_ARGS}
				then
					touch "${DBDIR}/requires/$origin/status/COMPLETE_INSTALL"
				else
					touch "${DBDIR}/requires/$origin/status/FAILED_INSTALL"
				fi
			fi
			if [ -e "${DBDIR}/requires/$origin/status/FAILED_INSTALL" ]
			then
				if [ ! -e "${DBDIR}/requires/$origin/status/COMPETE.FAILED_INSTALL.RECOVER" ]
				then
					echo "*** Trying to deinstall the failed/aborted installtion... (Ignore failures)"
					env ${MAKE_ENVS} make deinstall ${MAKE_ARGS} || :
					if [ "$currentpkg" ]
					then
						echo "*** Restoring the backup..."
						if [ -e "${DBDIR}/backup_failure/$currentpkg.tbz" ]
						then
							echo "WARN: No backup exists, gave up." >&2
						elif pkg_add -fF "${DBDIR}/backup_failure/$currentpkg.tbz"
						then
							:
						else
							echo "WARN: Failed to restore $currentpkg. Note that your system may experience troubles by this error." >&2
						fi
					fi
					touch "${DBDIR}/requires/$origin/status/COMPETE.FAILED_INSTALL.RECOVER"
				fi
				if [ "$currentpkg" -a ! -e "${DBDIR}/requires/$origin/status/COMPETE.FAILED_INSTALL.AFTERINSTALL" -a -e "${DBDIR}/requires/$origin/AFTERINSTALL.conflist" ]
				then
					echo "-- AFTERINSTALL operations (start)"
					sh -e "${DBDIR}/requires/$origin/AFTERINSTALL.conflist" || { echo "-- (This error is ignored)"; }
					echo "-- AFTERINSTALL operations (end)"
					touch "${DBDIR}/requires/$origin/status/COMPETE.FAILED_INSTALL.AFTERINSTALL"
				fi
				record_failure $origin noclean
				rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
				cmt_fail_reinst "$origin"
				continue
			fi
		fi
		if [ -e "${DBDIR}/requires/$origin/AFTERINSTALL.conflist" -a ! -e "${DBDIR}/requires/$origin/status/in_install.AFTERINSTALL" ]
		then
			echo "-- AFTERINSTALL operations (start)"
			sh -e "${DBDIR}/requires/$origin/AFTERINSTALL.conflist" || \
			{
				echo "ERROR: while AFTERINSTALL operations for ${PORTSDIR}/$origin." >&2
				record_failure $origin noclean
				rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
				cmt_fail_reinst "$origin"
				continue
			}
			echo "-- AFTERINSTALL operations (end)"
			touch "${DBDIR}/requires/$origin/status/in_install.AFTERINSTALL"
		fi
		record_success $origin
		rm_a_line "$origin" "${DBDIR}/reinst_todo.list"
		rm -f "${DBDIR}/backup_failure/$currentpkg.tbz"
		rm "${DBDIR}/requires/$origin/status/in_install"
		echo
	done
	cd "$savedir"
	touch "${DBDIR}/COMPLETE_REINSTALLATION"
	echo
fi

# Clean up obsolete or unused distfiles
if [ ! -e "${DBDIR}/COMPLETE_CLEANUP_OBSLETE_DISTFILES" ]
then
	echo "Starting to clean up obsolete or unused distfiles at `timestamp`"
	find "${DISTDIR}" -type f | grep -v -f "${DBDIR}/distfiles.grep.pattern" | while read distfile
	do
		echo "  $distfile"
		rm -f "$distfile"
	done
	touch "${DBDIR}/COMPLETE_CLEANUP_OBSLETE_DISTFILES"
	echo
fi

# Rebuild of package database
if [ ! -e "${DBDIR}/COMPLETE_REBUILD_PKGDB" ]
then
	echo "Starting to rebuild package database at `timestamp`"
	pkgdb -fu
	touch "${DBDIR}/COMPLETE_REBUILD_PKGDB"
	echo
fi

# Notice of failures
touch "${DBDIR}/failed.list"
if [ `cat "${DBDIR}/failed.list" | wc -l` -gt 0 ]
then
	echo "*** The following ports were not reinstalled successfully."
	echo " Please recover them manually."
	echo " You are recommended to read ${PORTSDIR}/UPDATING to resolve the problems."
	echo " If you remember the last date to have upgraded the concerned ports,"
	echo "pkg_updating(1) will be useful."
	echo " You should also consider whether the failed ports are realy required, viz.,"
	echo "not obsolete dependencies, by using pkg_info(1) with -R option."
	echo " After resolving the problems, execute"
	echo "        ${APPNAME} ok [recovered_port_globs]"
	echo "and restart by"
	echo "        ${APPNAME} redo"
	echo "****************"
	cat "${DBDIR}/failed.list"
	echo "*** Warned by ${APPNAME}"
	echo
fi

# End
terminate_process ()
{
}

echo "All done at `timestamp`"
