#!/bin/sh
#
# pfSense-upgrade
#
# part of pfSense (https://www.pfsense.org)
# Copyright (c) 2015-2016 Electric Sheep Fencing, LLC
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgment:
#    "This product includes software developed by the pfSense Project
#    for use in the pfSense® software distribution. (http://www.pfsense.org/).
#
# 4. The names "pfSense" and "pfSense Project" must not be used to
#    endorse or promote products derived from this software without
#    prior written permission. For written permission, please contact
#    coreteam@pfsense.org.
#
# 5. Products derived from this software may not be called "pfSense"
#    nor may "pfSense" appear in their names without prior written
#    permission of the Electric Sheep Fencing, LLC.
#
# 6. Redistributions of any form whatsoever must retain the following
#    acknowledgment:
#
# "This product includes software developed by the pfSense Project
# for use in the pfSense software distribution (http://www.pfsense.org/).
#
# THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.

usage() {
	echo "Usage: $(basename ${0}) [-46bdyf] [-u|-i PKG_NAME|-r PKG_NAME]" >&2
	echo "	-4          - Force IPv4"
	echo "	-6          - Force IPv6"
	echo "	-b          - Platform is booting" >&2
	echo "	-c          - Check if upgrade is necessary" >&2
	echo "	-d          - Turn on debug" >&2
	echo "	-f          - Force package installation" >&2
	echo "	-h          - Show this usage help" >&2
	echo "	-l          - Logfile path (defaults to /cf/conf/upgrade_log.txt)" >&2
	echo "	-n          - Dry run" >&2
	echo "	-p socket   - Write pkg progress to socket"
	echo "	-R          - Do not reboot (this can be dangerous)"
	echo "	-y          - Assume yes as the answer to any possible interaction" >&2
	echo "" >&2
	echo "The following parameters are mutually exclusive:" >&2
	echo "	-i PKG_NAME - Install package PKG_NAME" >&2
	echo "	-r PKG_NAME - Remove package PKG_NAME" >&2
	echo "	-u          - Update repository information" >&2
}

_echo() {
	local _n=""
	if [ "${1}" = "-n" ]; then
		shift
		_n="-n"
	fi

	if [ -z "${logfile}" ]; then
		logfile=/dev/null
	fi

	echo ${_n} "${1}" | tee -a ${logfile}
}

_exec() {
	local _cmd="${1}"
	local _msg="${2}"
	local _mute="${3}"
	local _ignore_result="${4}"
	local _stdout="${stdout}"

	if [ -z "${_cmd}" -o -z "${_msg}" ]; then
		return 1
	fi

	if [ "${_mute}" != "mute" ]; then
		_stdout=''
	fi

	_echo -n ">>> ${_msg}... "
	if [ -z "${_stdout}" ]; then
		_echo ""
		# Ref. http://stackoverflow.com/questions/1221833/bash-pipe-output-and-capture-exit-status
		exec 4>&1
		local _result=$({ { ${_cmd} 2>&1 3>&-; printf $? 1>&3; } 4>&- | \
			tee -a ${logfile} 1>&4; } 3>&1)
		exec 4>&-
	else
		# Ref. http://stackoverflow.com/questions/1221833/bash-pipe-output-and-capture-exit-status
		exec 4>&1
		local _result=$({ { ${_cmd} >${_stdout} 2>&1 3>&-; printf $? 1>&3; } 4>&- | \
			tee -a ${logfile} 1>&4; } 3>&1)
		exec 4>&-
	fi

	if [ ${_result} -eq 0 -o -n "${_ignore_result}" ]; then
		[ -n "${_stdout}" ] \
			&& _echo "done."
		return 0
	else
		[ -n "${_stdout}" ] \
			&& _echo "failed."
		_exit 1
	fi
}

_exit() {
	trap "-" 1 2 15 EXIT

	pkg_lock ${kernel_pkg}

	if [ -f "${pid_file}" ]; then
		rm -f ${pid_file}
	fi

	if [ -n "${chroot_dir}" ]; then
		umount -f ${chroot_dir} >/dev/null 2>&1
	fi

	if [ -z "${booting}" -o "${boot_stage}" != "2" ]; then
		/usr/local/bin/php /etc/rc.conf_mount_ro
	fi

	if [ -n "${nc_pid}" ] && ps -p ${nc_pid} >/dev/null 2>&1; then
		kill ${nc_pid}
	fi

	if [ -n "${delete_annotation}" ]; then
		pkg ${pkg_chroot} annotate -q -D ${kernel_pkg} next_stage
	fi

	if [ -n "${unlock_additional_pkgs}" ]; then
		pkg_unlock "${pkg_prefix}*"
	fi

	local _rc=${1:-"0"}

	# If EVENT_PIPE is defined, GUI is calling
	if [ -n "${progress_socket}" ]; then
		local _need_reboot_str=""
		[ -n "${need_reboot}" ] \
			&& _need_reboot_str=" __REBOOT_AFTER=${reboot_after}"
		_echo "__RC=${_rc}${_need_reboot_str}"
	fi

	exit ${_rc}
}

pkg_with_pb() {
	local _event_pipe=""

	if [ -n "${progress_socket}" ]; then
		if [ -e "${chroot_dir}${progress_socket}" ]; then
			rm -f ${chroot_dir}${progress_socket}
		fi

		_event_pipe="-o EVENT_PIPE=${progress_socket}"

		nc -lU ${chroot_dir}${progress_socket} >> ${progress_file} &
		nc_pid=$!

		while [ ! -e "${chroot_dir}${progress_socket}" ]; do
			sleep 0.1
		done
	fi

	pkg ${_event_pipe} $@
	local _pkg_result=$?
	nc_pid=""
	return ${_pkg_result}
}

fetch_upgrade_packages() {
	local _pkgs_to_fetch=""
	if [ "${platform}" = "nanobsd" ]; then
		local _pkg=""

		# Check if all non-auto packages installed on 2nd partition are
		# installed on current one, if not, mark them to be deleted by
		# pkg autoremove
		for _pkg in $(pkg ${pkg_chroot} query -e '%a == 0' %n); do
			if ! pkg info -e ${_pkg}; then
				_exec "pkg ${pkg_chroot} set -A 1 ${_pkg}" "Scheduling package ${_pkg} for removal"
			fi
		done

		# Check if all non-auto packages installed on current partition are
		# installed on 2nd one, if not, we need to fetch them
		for _pkg in $(pkg query -e '%a == 0' %n); do
			if ! pkg ${pkg_chroot} info -e ${_pkg}; then
				_pkgs_to_fetch="${_pkgs_to_fetch}${_pkgs_to_fetch:+ }${_pkg}"
			fi
		done

	fi

	_exec "pkg_with_pb ${pkg_chroot} upgrade -F" "Downloading upgrade packages"

	if [ -n "${_pkgs_to_fetch}" ]; then
		_exec "pkg_with_pb ${pkg_chroot} fetch -d ${_pkgs_to_fetch}" \
			"Fetching packages not present on upgrade partition"
	fi
}

pkg_lock() {
	local _pkg="${1}"

	if [ -z "${_pkg}" ]; then
		return
	fi

	if [ "$(pkg ${pkg_chroot} query %k ${_pkg})" = "0" ]; then
		_exec "pkg ${pkg_chroot} lock ${_pkg}" "Locking package ${_pkg}" mute
	fi
}

pkg_unlock() {
	local _pkg="${1}"

	if [ -z "${_pkg}" ]; then
		return
	fi

	if [ "$(pkg ${pkg_chroot} query %k ${_pkg})" = "1" ]; then
		_exec "pkg ${pkg_chroot} unlock ${_pkg}" "Unlocking package ${_pkg}" mute
	fi
}

pkg_update() {
	local _run_update=1

	local _force=""
	if [ "${1}" = "force" ]; then
		_force=" -f"
	fi

	_exec "pkg ${pkg_chroot} update${_force}" "Updating repositories metadata"
}

pkg_upgrade() {
	# figure out which kernel variant is running
	export kernel_pkg=$(pkg query %n $(pkg info ${product}-kernel-\* | grep -v -- -debug-))

	if [ -z "${kernel_pkg}" ]; then
		_echo "ERROR: It was not possible to identify which ${product} kernel is installed"
		_exit 1
	fi

	export next_stage=$(pkg annotate -q -S ${kernel_pkg} next_stage)

	if [ -n "${next_stage}" -a -n "${booting}" -a -n "${boot_stage}" ]; then
		if [ ${boot_stage} != ${next_stage} ]; then
			_exit 0
		fi
	fi

	# If it's booting and first stage didn't run, just exit
	if [ -n "${booting}" -a -z "${next_stage}" ]; then
		_exit 0
	fi

	unset need_reboot
	# First upgrade stage
	if [ -z "${next_stage}" ]; then
		if [ -f "${logfile}" ]; then
			rm -f ${logfile}
		fi

		pkg_update

		if [ "$(compare_pkg_version pkg)" = "<" ]; then
			_exec "pkg upgrade pkg" "Upgrading pkg" mute
			pkg_update force
		fi

		local _repo_pkg="${product}-repo"

		# Deprecated pa
		if is_pkg_installed ${product}-repo-devel; then
			_exec "pkg ${pkg_chroot} set -A 1 ${product}-repo-devel" \
				"Scheduling package ${product}-repo-devel for removal"
			_exec "pkg install ${_repo_pkg}" "Installing ${_repo_pkg}" mute
			_exec "pkg delete ${product}-repo-devel" "Removing ${product}-repo-devel" \
				mute ignore_result
			validate_repo_conf
			pkg_update force
		fi

		if [ "$(compare_pkg_version ${_repo_pkg})" = "<" ]; then
			cp /usr/local/etc/pkg/repos/${product}.conf \
				/tmp/${product}.conf.copy
			_exec "pkg upgrade ${_repo_pkg}" "Upgrading ${_repo_pkg}" mute
			# If conf differs, for an update
			if ! cmp -s /usr/local/etc/pkg/repos/${product}.conf /tmp/${product}.conf.copy; then
				pkg_update force

				# New repo may contain newer pkg
				if [ "$(compare_pkg_version pkg)" = "<" ]; then
					_exec "pkg upgrade pkg" "Upgrading pkg" mute
					pkg_update force
				fi
			fi
			rm -f /tmp/${product}.conf.copy
		fi

		if [ $(pkg upgrade -nq | wc -l) -le 1 ]; then
			_echo "Your packages are up to date"
			_exit 0
		fi

		if [ -n "${dry_run}" ]; then
			pkg_unlock ${kernel_pkg}
			pkg ${pkg_chroot} upgrade -nq 2>&1 | tee -a ${logfile}
			pkg_lock ${kernel_pkg}
			_exit 0
		fi

		local _meta_pkg=$(get_meta_pkg_name)
		if [ $(pkg upgrade -r ${product}-core -nq | wc -l) -gt 1 ]; then
			if [ "${platform}" = "nanobsd" ]; then
				_echo "**** WARNING ****"
				_echo "Duplicate slice required!!"
				_echo ""
				_echo "Before starting the upgrade process, the currently mounted nanobsd partition"
				_echo "needs to be cloned to the secondary partition, where the update will happen"
				_echo ""
				_echo "After installation a reboot will be required to switch partition."
				_echo ""
				if [ -z "${yes}" ]; then
					_echo -n "Proceed with upgrade? (y/N) "
					read answer
					if [ "${answer}" != "y" ]; then
						_echo "Aborting..."
						_exit 0
					fi
					# Do not make the user have to answer again.
					yes=1
				fi
				setup_nanobsd_env
			fi
			need_reboot=1
		elif pkg upgrade -r ${product} -nq ${_meta_pkg} >/dev/null 2>&1; then
			need_reboot=1
		fi

		pkg_unlock ${kernel_pkg}

		if [ -z "${yes}" ]; then
			# Show user which packages are going to be upgraded
			pkg ${pkg_chroot} upgrade -nq 2>&1 | tee -a ${logfile}

			_echo ""
			if [ -n "${need_reboot}" ]; then
				_echo "**** WARNING ****"
				_echo "Reboot will be required!!"
			fi
			_echo -n "Proceed with upgrade? (y/N) "
			read answer
			if [ "${answer}" != "y" ]; then
				_echo "Aborting..."
				_exit 0
			fi
		fi

		# Download all upgrade packages first
		fetch_upgrade_packages

		if [ $(pkg ${pkg_chroot} upgrade -nq ${kernel_pkg} | wc -l) -gt 1 ]; then
			_exec "pkg ${pkg_chroot} upgrade ${kernel_pkg}" "Upgrading ${product} kernel"
		fi

		pkg ${pkg_chroot} annotate -q -M ${kernel_pkg} next_stage 2
		next_stage=2

		if [ -n "${need_reboot}" -a "${platform}" != "nanobsd" ]; then
			do_reboot
			_exit 0
		fi
	fi

	if [ "${next_stage}" = "2" ]; then
		pkg_lock "${pkg_prefix}*"
		unlock_additional_pkgs=1

		# XXX: Workaround to upgrade strongswan
		# If those symlinks are present, pkg exit because it expects them
		# to be a directory
		if [ $(pkg ${pkg_chroot} upgrade -nq strongswan | wc -l) -gt 1 ]; then
			if [ -L ${chroot_dir}/usr/local/etc/ipsec.d ]; then
				rm -f ${chroot_dir}/usr/local/etc/ipsec.d
			fi
			if [ -L ${chroot_dir}/usr/local/etc/ipsec.conf ]; then
				rm -f ${chroot_dir}/usr/local/etc/ipsec.conf
			fi
			if [ -L ${chroot_dir}/usr/local/etc/strongswan.d ]; then
				rm -f ${chroot_dir}/usr/local/etc/strongswan.d
			fi
			if [ -L ${chroot_dir}/usr/local/etc/strongswan.conf ]; then
				rm -f ${chroot_dir}/usr/local/etc/strongswan.conf
			fi
		fi

		if [ $(pkg ${pkg_chroot} upgrade -nq | wc -l) -gt 1 ]; then
			delete_annotation=1
			_exec "pkg ${pkg_chroot} upgrade" "Upgrading necessary packages"
			delete_annotation=""
		fi

		# XXX: workaround for #5300
		sort -u ${chroot_dir}/usr/local/etc/php/extensions.ini > /tmp/extensions.ini
		mv /tmp/extensions.ini ${chroot_dir}/usr/local/etc/php/extensions.ini

		pkg ${pkg_chroot} annotate -q -M ${kernel_pkg} next_stage 3
		next_stage=3

		pkg_unlock "${pkg_prefix}*"
		unlock_additional_pkgs=""

		if [ -n "${need_reboot}" -a "${platform}" = "nanobsd" ]; then
			switch_active_nanobsd_partition
			do_reboot
			_exit 0
		fi

		if [ -n "${booting}" ]; then
			_exit 0
		fi
	fi

	if [ "${next_stage}" = "3" ]; then
		if [ $(pkg upgrade -nq | wc -l) -gt 1 ]; then
			delete_annotation=1
			_exec "pkg ${pkg_chroot} upgrade" "Upgrading necessary packages"
			delete_annotation=""
		fi

		pkg ${pkg_chroot} annotate -q -D ${kernel_pkg} next_stage

		# cleanup caches
		_exec "pkg ${pkg_chroot} autoremove" "Removing unnecessary packages" mute ignore_result
		_exec "pkg ${pkg_chroot} clean" "Cleanup pkg cache" mute ignore_result
	fi

	gitsync=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/synconupgrade)
	if [ "${gitsync}" = "true" ]; then
		repository_url=$(/usr/local/sbin/read_xml_tag.sh string system/gitsync/repositoryurl)
		branch=$(/usr/local/sbin/read_xml_tag.sh string system/gitsync/branch)

		# Repository URL is not mandatory
		if [ -n "${branch}" ]; then
			_exec "/usr/local/sbin/pfSsh.php playback gitsync \
				${repositoryurl} ${branch} --upgrading" \
				"Running gitsync" mute ignore_result
		fi
	fi
}

get_meta_pkg_name() {
	# figure out main meta package name
	if is_pkg_installed ${product}-vmware; then
		echo "${product}-vmware"
	elif is_pkg_installed ${product}; then
		echo "${product}"
	else
		_echo "ERROR: It was not possible to identify which ${product} meta package is installed"
		_exit 1
	fi
}

check_upgrade() {
	local _meta_pkg=$(get_meta_pkg_name)

	pkg_update

	if [ "$(compare_pkg_version ${_meta_pkg})" = "<" ]; then
		local _new_version=$(pkg rquery %v ${_meta_pkg})
		_echo "${_new_version} version of ${product} is available"
		_exit 2
	else
		for _pkg in $(pkg query -e "%n ~ ${product}-*" %n); do
			# Ignore additional packages
			if echo "${_pkg}" | grep -q "^${pkg_prefix}"; then
				continue
			fi
			if [ "$(compare_pkg_version ${_pkg})" = "<" ]; then
				local _new_version=$(pkg rquery %v ${_pkg})
				_echo "${_new_version} version of ${_pkg} is available"
				_exit 2
			fi
		done
	fi

	_echo "Your system is up to date"
	_exit 0
}

setup_nanobsd_env() {
	if [ "${platform}" != "nanobsd" ]; then
		return;
	fi

	chroot_dir=/tmp/nanobsd_upgrade
	mkdir -p ${chroot_dir} 2>/dev/null
	local _cur_partition=$(mount -p / | cut -f1)
	local _update_partition=$(echo ${_cur_partition} | sed -e 's,0$,2,; s,1$,0,; s,2$,1,')

	if [ ! -e "${_update_partition}" ]; then
		_echo "Secondary partition (${_update_partition}), used for upgrade not found"
		_exit 1
	fi

	# Remove /dev
	_update_partition=$(echo ${_update_partition} | sed 's,^/dev/,,')
	local _update_slice=$(glabel status -s | awk "\$1 == \"${_update_partition}\" { print \$3 }")

	if [ -z "${_update_slice}" -o ! -e "/dev/${_update_slice}" ]; then
		_echo "Secondary slice (${_update_slice}), use_update_sliced for upgrade not found"
		_exit 1
	fi

	_update_slice="/dev/${_update_slice}"

	# Clone slice using same logic from nanobsd_clone_slice()
	sysctl kern.geom.debugflags=16 >/dev/null 2>&1
	_exec "dd if=/dev/zero of=${_update_slice} bs=1m count=1" "Cleaning secondary partition" mute
	_exec "dd if=${_cur_partition} of=${_update_slice} bs=64k" "Duplicating current slice" mute
	_exec "tunefs -L ${_update_partition##*/} ${_update_slice}" "Restoring slice label" mute
	sysctl kern.geom.debugflags=0 >/dev/null 2>&1

	_exec "/sbin/fsck -y -t ufs /dev/${_update_partition}" "Testing duplicated partition integrity" mute
	_exec "mount /dev/${_update_partition} ${chroot_dir}" "Mounting second partition to run upgrade" mute

	# Make sure resolv.conf is present, otherwise upgrade may fail (bug #6557)
	local _resolv_conf=$(readlink -f /etc/resolv.conf)
	_exec "cp -f ${_resolv_conf} ${chroot_dir}/etc/resolv.conf" \
		"Copying resolv.conf to upgrade partition" mute ignore_result

	sed -i '' -e "s,^${_cur_partition},/dev/${_update_partition}," \
		${chroot_dir}/etc/fstab

	pkg_chroot="-c ${chroot_dir}"
}

switch_active_nanobsd_partition() {
	if [ "${platform}" != "nanobsd" ]; then
		return;
	fi

	local _cur_partition=$(mount -p / | cut -f1 | sed 's,^/dev/,,')
	local _disk=$(glabel status -s | \
		awk "\$1 == \"${_cur_partition}\" { print substr(\$3, 0, length(\$3)-3)}")
	local _i=$(echo ${_cur_partition} | cut -c ${#_cur_partition})

	if ! echo "${_i}" | egrep -q '^[0-9]$'; then
		_echo "Invalid partition label ${_cur_partition}"
		_exit 1
	fi

	# pfsense0 == part 1 / pfsense1 == part 2
	if [ ${_i} -eq 0 ]; then
		_i=2
	else
		_i=1
	fi

	_exec "gpart set -a active -i ${_i} ${_disk}" "Setting secondary partition as active" mute
}

is_pkg_installed() {
	local _pkg_name="${1}"
	shift
	local _pkg_chroot="$@"

	pkg ${_pkg_chroot} info -e ${_pkg_name}
	return $?
}

compare_pkg_version() {
	local _pkg_name="${1}"

	if ! is_pkg_installed ${_pkg_name} ${pkg_chroot}; then
		echo '!'
		return 1
	fi

	local _lver=$(pkg ${pkg_chroot} query %v ${_pkg_name})

	if [ -z "${_lver}" ]; then
		_echo "ERROR: It was not possible to determine ${_pkg_name} local version"
		_exit 1
	fi

	local _rver=$(pkg ${pkg_chroot} rquery %v ${_pkg_name})

	if [ -z "${_rver}" ]; then
		_echo "ERROR: It was not possible to determine ${_pkg_name} remote version"
		_exit 1
	fi

	local _version=$(pkg version -t ${_lver} ${_rver})

	if [ $? -ne 0 ]; then
		_echo "ERROR: Error comparing ${_pkg_name} local and remote versions"
		_exit 1
	fi

	echo ${_version}
	return 0
}

pkg_install() {
	local _pkg_name="${1}"

	local _force=""
	if [ -n "${2}" ]; then
		_force="-f"
	fi

	if [ -z "${_pkg_name}" ]; then
		_echo "ERROR: Blank package name"
		_exit 1
	fi

	if is_pkg_installed ${_pkg_name}; then
		local _cversion=$(compare_pkg_version ${_pkg_name})

		if [ -z "${_force}" ]; then
			if [ "${_cversion}" = "=" ]; then
				_echo "Package ${_pkg_name} is up to date"
				_exit 0
			elif [ "${_cversion}" = ">" ]; then
				_echo "Installed ${_pkg_name} version is newer than remote"
				_exit 0
			fi
		fi
		local _cmd="upgrade ${_force}"
		local _msg="Upgrading"
	else
		local _cmd="install"
		local _msg="Installing"
	fi

	_exec "pkg_with_pb ${_cmd}${dry_run:+ }${dry_run} ${_pkg_name}" "${_msg} ${_pkg_name}"
	_exec "pkg clean" "Cleaning up cache" mute ignore_result
}

# Reinstall every pfSense-pkg-* package
pkg_reinstall_all() {
	for _pkg in $(pkg query -e '%a == 0' %n); do
		case ${_pkg} in "${pkg_prefix}"* )
			_echo "Reinstalling ${_pkg}"
			pkg_install ${_pkg} 1
			;;
		esac
	done
}

pkg_delete() {
	local _pkg_name="${1}"

	if [ -z "${_pkg_name}" ]; then
		_echo "ERROR: Blank package name"
		_exit 1
	fi

	if ! is_pkg_installed ${_pkg_name}; then
		_echo "ERROR: Package ${_pkg_name} is not installed"
		_exit 1
	fi

	_exec "pkg_with_pb delete${dry_run:+ }${dry_run} ${_pkg_name}" "Removing ${_pkg_name}"
	_exec "pkg autoremove" "Removing stale packages" mute ignore_result
}

# Delete every pfSense-pkg-* package
pkg_delete_all() {
	for _pkg in $(pkg query -e '%a == 0' %n); do
		case ${_pkg} in "${pkg_prefix}"* )
			_echo "Removing ${_pkg}"
			pkg_delete ${_pkg}
			;;
		esac
	done
}

do_reboot() {
	if [ -z "${dont_reboot}" ]; then
		_echo "Upgrade is complete.  Rebooting in ${reboot_after} seconds."
		echo "Upgrade is complete.  Rebooting in ${reboot_after} seconds." | wall
		/etc/rc.notify_message -e -g -m "Upgrade is complete.  Rebooting in ${reboot_after} seconds." \
			>/dev/null 2>&1
		(sleep ${reboot_after} && /etc/rc.reboot) &
	else
		_echo "Upgrade is complete."
		echo "Upgrade is complete." | wall
		/etc/rc.notify_message -e -g -m "Upgrade is complete." >/dev/null 2>&1
	fi
}

validate_repo_conf() {
	# Make sure to use default repo conf when it doesn't exist
	pkg_repo_conf="/usr/local/etc/pkg/repos/${product}.conf"
	default_pkg_repo_conf_path="/usr/local/share/${product}/pkg/repos/${product}-repo.conf"

	pkg_repo_conf_path=$(/usr/local/sbin/read_xml_tag.sh string system/pkg_repo_conf_path)

	if [ -z "${pkg_repo_conf_path}" -o ! -f "${pkg_repo_conf_path}" ]; then
		pkg_repo_conf_path=${default_pkg_repo_conf_path}
	fi

	if [ -f "${pkg_repo_conf_path}" ]; then
		if [ -e "${pkg_repo_conf}" -a ! -L "${pkg_repo_conf}" ]; then
			rm -f ${pkg_repo_conf}
			ln -sf ${pkg_repo_conf_path} ${pkg_repo_conf}
		fi

		if [ "$(readlink ${pkg_repo_conf})" != "${pkg_repo_conf_path}" ]; then
			mkdir -p /usr/local/etc/pkg/repos
			ln -sf ${pkg_repo_conf_path} ${pkg_repo_conf}
		fi
	fi
}

export LANG=C

pid_file="/var/run/$(basename $0).pid"
logfile="/cf/conf/upgrade_log.txt"
stdout='/dev/null'

# Setup proxy settings
HTTP_PROXY=$(/usr/local/sbin/read_xml_tag.sh string system/proxyurl)
if [ "${HTTP_PROXY}" != "" ]; then
	HTTP_PROXY_PORT=$(/usr/local/sbin/read_xml_tag.sh string system/proxyport)
	if [ "${HTTP_PROXY_PORT}" != "" ]; then
		HTTP_PROXY="${HTTP_PROXY}:${HTTP_PROXY_PORT}"
	fi
	export HTTP_PROXY
fi

# pkg should not ask for confirmations
export ASSUME_ALWAYS_YES=true
export FETCH_TIMEOUT=5
export FETCH_RETRY=2

export product=$(/usr/local/bin/php -n /usr/local/sbin/read_global_var product_name pfSense)
export pkg_prefix=$(/usr/local/bin/php -n /usr/local/sbin/read_global_var pkg_prefix pfSense-pkg-)
export platform=$(cat /etc/platform)

USE_MFS_TMPVAR=$(/usr/local/sbin/read_xml_tag.sh boolean system/use_mfs_tmpvar)
if [ "${platform}" = "nanobsd" ] || [ "${USE_MFS_TMPVAR}" = "true" ]; then
	export PKG_DBDIR=/root/var/db/pkg
	export PKG_CACHEDIR=/root/var/cache/pkg
fi

product_version=$(cat /etc/version)
do_not_send_host_uuid=$(/usr/local/sbin/read_xml_tag.sh boolean system/do_not_send_host_uuid)
if [ "${do_not_send_host_uuid}" != "true" ]; then
	hostuuid=$(sysctl kern.hostuuid)
	export HTTP_USER_AGENT="${product}/${product_version}:${hostuuid}"
else
	export HTTP_USER_AGENT="${product}/${product_version}"
fi

validate_repo_conf

# Flags used in _exit
export delete_annotation=""
export unlock_additional_pkgs=""

# Upgrade process on nanobsd will happen in chroot
export pkg_chroot=""
export chroot_dir=""

# Save nc_pid to be able to kill it
export nc_pid=""

# Reboot after 10 seconds
export reboot_after=10

unset dry_run
unset dont_reboot
unset booting
unset boot_stage
unset force
unset yes
unset progress_file
unset progress_socket
unset action
unset action_pkg
unset force_ipv4
unset force_ipv6
while getopts 46b:cdfi:hp:l:nr:Ruy opt; do
	case ${opt} in
		4)
			if [ -n "${force_ipv6}" ]; then
				usage
				_exit 1
			fi
			force_ipv4=1
			;;
		6)
			if [ -n "${force_ipv4}" ]; then
				usage
				_exit 1
			fi
			force_ipv6=1
			;;
		b)
			booting=1
			boot_stage="${OPTARG}"
			;;
		c)
			action="check"
			;;
		d)
			stdout=''
			;;
		f)
			force=1
			;;
		i)
			if [ -n "${action}" ]; then
				usage
				_exit 1
			fi
			action="install"
			action_pkg="${OPTARG}"
			;;
		h)
			usage
			_exit 0
			;;
		l)
			logfile="${OPTARG}"
			if [ -z "${logfile}" ]; then
				usage
				_exit 1
			fi
			;;
		n)
			dry_run="-n"
			;;
		p)
			progress_socket="${OPTARG}"
			if [ -z "${progress_socket}" ]; then
				usage
				_exit 1
			fi
			;;
		r)
			if [ -n "${action}" ]; then
				usage
				_exit 1
			fi
			action="delete"
			action_pkg="${OPTARG}"
			;;
		R)
			dont_reboot=1
			;;
		u)
			if [ -n "${action}" ]; then
				usage
				_exit 1
			fi
			action="update"
			;;
		y)
			yes=1
			;;
		*)
			usage
			_exit 1
			;;
	esac
done

if [ -n "${force_ipv4}" ]; then
	export IP_VERSION="4"
elif [ -n "${force_ipv6}" ]; then
	export IP_VERSION="6"
fi

# Set default action when no parameter is set
: ${action:="upgrade"}

if pgrep -qF ${pid_file} >/dev/null 2>&1; then
	echo "Another instance is already running... Aborting!"
	exit 1
fi

if [ -z "${booting}" -o "${boot_stage}" != "2" ]; then
	/usr/local/bin/php /etc/rc.conf_mount_rw
fi

if [ -n "${booting}" ]; then
	export REPO_AUTOUPDATE=false
fi

echo $$ > ${pid_file}

trap _exit 1 2 15 EXIT

if [ "${action}" != "upgrade" -a -f "${logfile}" ]; then
	rm -f ${logfile}
fi

progress_file=${logfile%.*}.json

if [ -e "${progress_file}" ]; then
	rm -f ${progress_file}
fi

case "${action}" in
	check)
		check_upgrade
		;;
	upgrade)
		pkg_upgrade
		;;
	update)
		pkg_update force
		;;
	install)
		if [ ${action_pkg} == "ALL_PACKAGES" ] && [ -n ${force} ]; then
			pkg_reinstall_all
		else
			pkg_install ${action_pkg} ${force}
		fi
		;;
	delete)
		if [ ${action_pkg} == "ALL_PACKAGES" ] && [ -n ${force} ]; then
			pkg_delete_all
		else
			pkg_delete ${action_pkg}
		fi
		;;
	*)
		_echo "ERROR: Invalid action!"
		_exit 1
esac

_exit 0
