#!/bin/sh
#
# pfSense-upgrade
#
# part of pfSense (https://www.pfsense.org)
# Copyright (c) 2015-2016 Rubicon Communications, LLC (Netgate)
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

usage() {
	echo "Usage: $(basename ${0}) [-46bdfhnRUy] [-l LOG_PATH] [-p SOCKET_PATH] [-c|-u|-i PKG_NAME|-r PKG_NAME]" >&2
	echo "	-4          - Force IPv4"
	echo "	-6          - Force IPv6"
	echo "	-b          - Platform is booting" >&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 "	-U          - Do not update repository information" >&2
	echo "	-y          - Assume yes as the answer to any possible interaction" >&2
	echo "" >&2
	echo "The following parameters are mutually exclusive:" >&2
	echo "	-c          - Check if upgrade is necessary" >&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

	if [ -n "${lock_main_pkgs}" ]; then
		pkg_lock ${kernel_pkg} ${uboot_pkg}
	fi

	if [ -n "${delete_pid}" -a -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 _pkgs="$@"

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

	for _pkg in ${_pkgs}; do
		for _pkg_name in $(pkg query -g %n "${_pkg}"); do
			if [ "$(pkg ${pkg_chroot} query %k ${_pkg_name})" != "0" ]; then
				continue
			fi
			_exec "pkg ${pkg_chroot} lock ${_pkg_name}" \
				"Locking package ${_pkg_name}" mute
		done
	done
}

pkg_unlock() {
	local _pkgs="$@"

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

	for _pkg in ${_pkgs}; do
		for _pkg_name in $(pkg query -g %n "${_pkg}"); do
			if [ "$(pkg ${pkg_chroot} query %k ${_pkg_name})" != "1" ]; then
				continue
			fi
			_exec "pkg ${pkg_chroot} unlock ${_pkg_name}" \
				"Unlocking package ${_pkg_name}" mute
		done
	done
}

abi_setup() {
	local _freebsd_version=$(uname -r)
	local _pkg_repo_conf="/usr/local/etc/pkg/repos/${product}.conf"

	local _arch=$(uname -p)

	CUR_ABI="FreeBSD:${_freebsd_version%%.*}:${_arch}"

	if [ "${_arch}" == "armv6" ]; then
		CUR_ALTABI="freebsd:${_freebsd_version%%.*}:${_arch}:32:el:eabi:hardfp"
	elif [ "${_arch}" == "i386" ]; then
		CUR_ALTABI="freebsd:${_freebsd_version%%.*}:x86:32"
	else
		CUR_ALTABI="freebsd:${_freebsd_version%%.*}:x86:64"
	fi

	if [ ! -e ${_pkg_repo_conf} ]; then
		validate_repo_conf
	fi

	local _repo_abi_file=$(readlink ${_pkg_repo_conf})

	if [ -f ${_repo_abi_file%%.conf}.abi ]; then
		ABI=$(cat ${_repo_abi_file%%.conf}.abi)
	else
		ABI=${CUR_ABI}
	fi

	if [ -f ${_repo_abi_file%%.conf}.altabi ]; then
		ALTABI=$(cat ${_repo_abi_file%%.conf}.altabi)
	else
		ALTABI=${CUR_ALTABI}
	fi

	# Make sure pkg.conf is set properly so GUI can work
	echo "ABI=${ABI}" > /usr/local/etc/pkg.conf
	echo "ALTABI=${ALTABI}" >> /usr/local/etc/pkg.conf

	if [ "${CUR_ABI}" = "${ABI}" -o "${CUR_ABI}" = "${ALTABI}" ] ; then
		NEW_MAJOR=""
	else
		NEW_MAJOR=1
		ABI="${ALTABI}"
	fi

	export CUR_ABI CUR_ALTABI ABI ALTABI NEW_MAJOR
}

pkg_update() {
	local _force=""
	local _mute=""

	[ "${1}" = "force" ] \
		&& _force=" -f"

	if [ -z "${_force}" -a -n "${dont_update}" ]; then
		return 0
	fi

	[ "${2}" = "mute" ] \
		&& _mute="mute"

	abi_setup

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

pkg_upgrade_repo() {
	if [ -z "${NEW_MAJOR}" -a "$(compare_pkg_version pkg)" = "<" ]; then
		_exec "pkg upgrade${dont_update} 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${dont_update} ${_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

	[ -n "${NEW_MAJOR}" ] \
		&& return

	case "$(compare_pkg_version ${_repo_pkg})" in
		"<")
			local _force=""
			;;
		">")
			local _force=" -f"
			;;
		"=")
			return
			;;
		*)
			_echo "ERROR: Unable to compare version of ${_repo_pkg}"
			_exit 1
	esac

	cp /usr/local/etc/pkg/repos/${product}.conf /tmp/${product}.conf.copy
	_exec "pkg upgrade${dont_update}${_force} ${_repo_pkg}" \
	    "Upgrading ${_repo_pkg}" mute
	abi_setup
	# 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 [ -z "${NEW_MAJOR}" -a "$(compare_pkg_version pkg)" = "<" ]; then
			_exec "pkg upgrade pkg" "Upgrading pkg" mute
			pkg_update force
		fi
	fi
	rm -f /tmp/${product}.conf.copy
}

upgrade_available() {
	# XXX locked packages message should be printed on stderr by pkg?
	local _lines=$(pkg upgrade${dont_update} -nq "$@" 2>/dev/null \
	    | sed -e '/^$/d; /is locked and may not be modified/d' \
	    | wc -l)

	test ${_lines} -gt 0
	return $?
}

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

	# Detect if has u-boot package installed
	unset uboot_pkg
	if pkg info ${product}-u-boot-\* >/dev/null 2>&1; then
		export uboot_pkg=$(pkg query %n $(pkg info ${product}-u-boot-\*))
	fi

	if [ -n "${uboot_pkg}" -a \
	    "$(compare_pkg_version ${uboot_pkg})" = "<" ]; then
		if [ ! -f /boot/msdos/u-boot.img ]; then
			# Try to mount /boot/msdos
			mount /boot/msdos >/dev/null 2>&1
		fi

		if [ ! -f /boot/msdos/u-boot.img ]; then
			_echo "ERROR: u-boot partition (/boot/msdos) is not properly mounted"
			_exit 1
		fi
	fi

	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)
	export is_pkg_locked=$(pkg annotate -q -S ${kernel_pkg} new_major)

	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

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

		pkg_update

		# Lock pkg to avoid having pkg binary for version N+1 installed on N
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_lock pkg
		fi

		pkg_upgrade_repo

		pkg_unlock ${kernel_pkg} ${uboot_pkg}
		lock_main_pkgs=1

		if ! upgrade_available; then
			pkg_lock ${kernel_pkg} ${uboot_pkg}
			_echo "Your packages are up to date"
			_exit 0
		fi

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

		local _meta_pkg=$(get_meta_pkg_name)
		if [ "${platform}" = "nanobsd" ] \
		    && upgrade_available -r "${product}-core"; 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

		# ZFS panics with reroot, do not use it in this case
		# https://redmine.pfsense.org/issues/6045
		if ! kldstat -qm zfs; then
			# If kernel is not going to be upgraded, reroot
			if ! upgrade_available ${kernel_pkg}; then
				reboot_params="-r"
			fi
		fi

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

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

		# Unlock pkg to make sure it's downloaded
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_unlock pkg
		fi

		# Download all upgrade packages first
		fetch_upgrade_packages

		# Then lock it again
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_lock pkg
		fi

		if upgrade_available ${kernel_pkg}; then
			_exec "pkg ${pkg_chroot} upgrade -U ${kernel_pkg}" "Upgrading ${product} kernel"
		fi

		if [ -n "${uboot_pkg}" ] && upgrade_available ${uboot_pkg}; then
			_exec "pkg ${pkg_chroot} upgrade -U ${uboot_pkg}" "Upgrading ${product} u-boot"
		fi

		if [ -n "${NEW_MAJOR}" ]; then
			pkg ${pkg_chroot} annotate -q -M ${kernel_pkg} new_major 1
		fi

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

		if [ "${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 upgrade_available strongswan; 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 upgrade_available; then
			delete_annotation=1
			if [ -n "${is_pkg_locked}" ]; then
				# Upgrade pkg
				pkg_unlock pkg
				pkg ${pkg_chroot} annotate -q -D \
					${kernel_pkg} new_major
				_exec "pkg-static install -f pkg" \
					"Reinstalling pkg due to ABI change"

				# Upgrade base packages using pkg-static
				_exec "pkg-static ${pkg_chroot} upgrade -r ${product}-core" \
					"Upgrading necessary core packages"

				# Make sure PHP setup is fine
				/etc/rc.php_ini_setup >/dev/null 2>&1
			fi

			_exec "pkg ${pkg_chroot} upgrade${dont_update}" "Upgrading necessary packages"
			delete_annotation=""
		fi

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

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

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

		# 2nd reboot during new major version upgrade
		if [ -n "${is_pkg_locked}" ]; then
			# Reboot immediately
			do_reboot now
			_exit 0
		fi

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

	if [ "${next_stage}" = "3" ]; then
		if upgrade_available; then
			delete_annotation=1
			_exec "pkg ${pkg_chroot} upgrade${dont_update}" "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)

		minimal=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/minimal)
		diff=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/diff)
		show_files=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/show_files)
		show_command=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/show_command)
		dryrun=$(/usr/local/sbin/read_xml_tag.sh boolean system/gitsync/dryrun)

		mute='mute'
		options=''
		if [ "${minimal}" = "true" ]; then
			options=${options}' --minimal'
		fi
		if [ "${diff}" = "true" ]; then
			options=${options}' --diff'
		fi
		if [ "${show_files}" = "true" ]; then
			options=${options}' --show-files'
			mute='off'
		fi
		if [ "${show_command}" = "true" ]; then
			options=${options}' --show-command'
			mute='off'
		fi
		if [ "${dryrun}" = "true" ]; then
			options=${options}' --dry-run'
		fi

		# Repository URL is not mandatory
		if [ -n "${branch}" ]; then
			_exec "/usr/local/sbin/pfSsh.php playback gitsync \
				${repositoryurl} ${branch} ${options} --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)
	local _core_pkgs=$(pkg query -e "%n ~ ${product}-kernel-* || %n ~ ${product}-base*" %n)

	pkg_update "" mute

	pkg_upgrade_repo

	for _pkg in ${_meta_pkg} ${_core_pkgs}; do
		if [ "$(compare_pkg_version ${_pkg})" = "<" ]; then
			local _new_version=$(pkg rquery -U %v ${_pkg})
			_echo "${_new_version} version of ${product} is available"
			_exit 2
		fi
	done

	_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 "unlink ${chroot_dir}/etc/resolv.conf" \
		"Removing resolv.conf symlink from upgrade partition" mute ignore_result
	_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 [ -z "${_pkg_name}" ]; then
		echo '!'
		return 1
	fi

	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 -U %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() {
	local _now="$1"

	local _msg=""
	if [ "${_now}" = "now" ]; then
		_msg="now."
	else
		_msg="in ${reboot_after} seconds."
	fi

	if [ -z "${dont_reboot}" ]; then
		_echo "Upgrade is complete.  Rebooting ${_msg}"
		echo "Upgrade is complete.  Rebooting ${_msg}" | wall
		/etc/rc.notify_message -e -g -m "Upgrade is complete.  Rebooting ${_msg}" \
			>/dev/null 2>&1
		if [ "${_now}" = "now" ]; then
			/etc/rc.reboot ${reboot_params}
		else
			(sleep ${reboot_after} && /etc/rc.reboot ${reboot_params}) &
		fi
	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 lock_main_pkgs=""
export unlock_additional_pkgs=""
export delete_pid=""

# 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

# Used to set -r and reroot
export reboot_params=""

unset dry_run
unset dont_reboot
unset dont_update
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:RuUy 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"
			;;
		U)
			dont_update=" -U"
			;;
		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

echo $$ > ${pid_file}

# Since this point it's safe to remove pid_file
delete_pid=1

trap _exit 1 2 15 EXIT

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

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

abi_setup

if [ -n "${NEW_MAJOR}" -a "${action}" = "install" ]; then
	_echo "WARNING: Current pkg repository has a new OS major version."
	_echo "         ${product} should be upgraded before doing any other"
	_echo "         operation"
	_exit 1
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
